Files
wp-bnb/src/Plugin.php
magdev 2865956c56
All checks were successful
Create Release Package / build-release (push) Successful in 1m11s
Add WooCommerce integration for payments, invoices, and order management (v0.11.0)
- Product sync: Virtual WC products for rooms with bidirectional linking
- Cart/Checkout: Booking data in cart items, availability validation, dynamic pricing
- Orders: Automatic booking creation on payment, status mapping, guest record creation
- Invoices: PDF generation via mPDF, auto-attach to emails, configurable numbering
- Refunds: Full refund cancels booking, partial refund records amount only
- Admin: Cross-linked columns and row actions between bookings and orders
- Settings: WooCommerce tab with subtabs (General, Products, Orders, Invoices)
- HPOS compatibility declared for High-Performance Order Storage

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-03 22:40:36 +01:00

2558 lines
91 KiB
PHP

<?php
/**
* Main Plugin class.
*
* @package Magdev\WpBnb
*/
declare( strict_types=1 );
namespace Magdev\WpBnb;
use Magdev\WpBnb\Admin\Calendar as CalendarAdmin;
use Magdev\WpBnb\Admin\Dashboard as DashboardAdmin;
use Magdev\WpBnb\Admin\Reports as ReportsAdmin;
use Magdev\WpBnb\Admin\Seasons as SeasonsAdmin;
use Magdev\WpBnb\Blocks\BlockRegistrar;
use Magdev\WpBnb\Booking\Availability;
use Magdev\WpBnb\Booking\EmailNotifier;
use Magdev\WpBnb\Frontend\Search;
use Magdev\WpBnb\Frontend\Shortcodes;
use Magdev\WpBnb\Api\RestApi;
use Magdev\WpBnb\Integration\CF7;
use Magdev\WpBnb\Integration\Prometheus;
use Magdev\WpBnb\Integration\WooCommerce\Manager as WooCommerceManager;
use Magdev\WpBnb\Frontend\Widgets\AvailabilityCalendar;
use Magdev\WpBnb\Frontend\Widgets\BuildingRooms;
use Magdev\WpBnb\Frontend\Widgets\SimilarRooms;
use Magdev\WpBnb\License\Manager as LicenseManager;
use Magdev\WpBnb\License\Updater as LicenseUpdater;
use Magdev\WcLicensedProductClient\Dto\UpdateInfo;
use Magdev\WpBnb\PostTypes\Booking;
use Magdev\WpBnb\PostTypes\Building;
use Magdev\WpBnb\PostTypes\Guest;
use Magdev\WpBnb\PostTypes\Room;
use Magdev\WpBnb\PostTypes\Service;
use Magdev\WpBnb\Pricing\Calculator;
use Magdev\WpBnb\Privacy\Manager as PrivacyManager;
use Magdev\WpBnb\Pricing\Season;
use Magdev\WpBnb\Taxonomies\Amenity;
use Magdev\WpBnb\Taxonomies\RoomType;
use Magdev\WpBnb\Taxonomies\ServiceCategory;
use Twig\Environment;
use Twig\Loader\FilesystemLoader;
/**
* Main Plugin singleton class.
*/
final class Plugin {
/**
* Plugin instance.
*
* @var Plugin|null
*/
private static ?Plugin $instance = null;
/**
* Twig environment.
*
* @var Environment|null
*/
private ?Environment $twig = null;
/**
* Get plugin instance.
*
* @return Plugin
*/
public static function get_instance(): Plugin {
if ( null === self::$instance ) {
self::$instance = new self();
}
return self::$instance;
}
/**
* Private constructor to enforce singleton.
*/
private function __construct() {
$this->init_hooks();
$this->init_components();
}
/**
* Initialize WordPress hooks.
*
* @return void
*/
private function init_hooks(): void {
// Load text domain.
add_action( 'init', array( $this, 'load_textdomain' ) );
// Register assets.
add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_admin_assets' ) );
add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_frontend_assets' ) );
// Add plugin action links.
add_filter( 'plugin_action_links_' . WP_BNB_BASENAME, array( $this, 'add_action_links' ) );
// Register custom post types and taxonomies.
$this->register_post_types();
$this->register_taxonomies();
}
/**
* Register custom post types.
*
* @return void
*/
private function register_post_types(): void {
Building::init();
Room::init();
Booking::init();
Guest::init();
Service::init();
}
/**
* Register custom taxonomies.
*
* @return void
*/
private function register_taxonomies(): void {
// Taxonomies must be registered before post types that use them.
Amenity::init();
RoomType::init();
ServiceCategory::init();
}
/**
* Initialize plugin components.
*
* @return void
*/
private function init_components(): void {
// Initialize License Manager (always active for admin).
LicenseManager::get_instance();
// Initialize auto-updater (requires license configuration).
$this->init_updater();
// Initialize Contact Form 7 integration if CF7 is active.
// This runs in both admin (for tag generators) and frontend (for form rendering).
if ( class_exists( 'WPCF7' ) ) {
CF7::init();
}
// Initialize Prometheus metrics integration.
Prometheus::init();
// Initialize WooCommerce integration if WooCommerce is active.
if ( class_exists( 'WooCommerce' ) ) {
WooCommerceManager::init();
}
// Initialize REST API.
$this->init_rest_api();
// Initialize admin components.
if ( is_admin() ) {
$this->init_admin();
}
// Initialize frontend components only if licensed.
if ( ! is_admin() && LicenseManager::is_license_valid() ) {
$this->init_frontend();
}
}
/**
* Initialize the plugin auto-updater.
*
* @return void
*/
private function init_updater(): void {
$updater = new LicenseUpdater(
plugin_file: WP_BNB_PATH . 'wp-bnb.php',
current_version: WP_BNB_VERSION,
);
$updater->init();
}
/**
* Initialize the REST API.
*
* @return void
*/
private function init_rest_api(): void {
$api = new RestApi();
$api->init();
}
/**
* Initialize admin components.
*
* @return void
*/
private function init_admin(): void {
// Admin menu and settings will be added here.
add_action( 'admin_menu', array( $this, 'register_admin_menu' ) );
add_action( 'admin_menu', array( $this, 'reorder_admin_menu' ), 99 );
add_action( 'admin_init', array( $this, 'register_settings' ) );
// Initialize seasons admin page.
SeasonsAdmin::init();
// Initialize calendar admin page.
CalendarAdmin::init();
// Initialize email notifier.
EmailNotifier::init();
// Initialize privacy manager for GDPR compliance.
PrivacyManager::init();
// Register AJAX handlers.
add_action( 'wp_ajax_wp_bnb_check_availability', array( $this, 'ajax_check_availability' ) );
add_action( 'wp_ajax_wp_bnb_search_guest', array( $this, 'ajax_search_guest' ) );
}
/**
* Initialize frontend components.
*
* @return void
*/
private function init_frontend(): void {
// Initialize search (registers AJAX handlers).
Search::init();
// Initialize shortcodes.
Shortcodes::init();
// Initialize Gutenberg blocks.
BlockRegistrar::init();
// Register widgets.
add_action( 'widgets_init', array( $this, 'register_widgets' ) );
}
/**
* Register frontend widgets.
*
* @return void
*/
public function register_widgets(): void {
register_widget( SimilarRooms::class );
register_widget( BuildingRooms::class );
register_widget( AvailabilityCalendar::class );
}
/**
* Load plugin text domain.
*
* @return void
*/
public function load_textdomain(): void {
load_plugin_textdomain(
'wp-bnb',
false,
dirname( WP_BNB_BASENAME ) . '/languages'
);
}
/**
* Enqueue admin assets.
*
* @param string $hook_suffix Current admin page hook.
* @return void
*/
public function enqueue_admin_assets( string $hook_suffix ): void {
global $post_type;
// Check if we're on plugin pages or editing our custom post types.
$is_plugin_page = strpos( $hook_suffix, 'wp-bnb' ) !== false;
$is_our_post_type = in_array( $post_type, array( Building::POST_TYPE, Room::POST_TYPE, Booking::POST_TYPE, Guest::POST_TYPE, Service::POST_TYPE ), true );
$is_edit_screen = in_array( $hook_suffix, array( 'post.php', 'post-new.php' ), true );
$is_dashboard = 'toplevel_page_wp-bnb' === $hook_suffix;
if ( ! $is_plugin_page && ! ( $is_our_post_type && $is_edit_screen ) ) {
return;
}
wp_enqueue_style(
'wp-bnb-admin',
WP_BNB_URL . 'assets/css/admin.css',
array(),
WP_BNB_VERSION
);
$script_deps = array( 'jquery' );
// Add media dependencies for room gallery.
if ( Room::POST_TYPE === $post_type && $is_edit_screen ) {
wp_enqueue_media();
$script_deps[] = 'jquery-ui-sortable';
}
// Add Chart.js for dashboard.
if ( $is_dashboard ) {
wp_enqueue_script(
'chartjs',
'https://cdn.jsdelivr.net/npm/chart.js@4.4.1/dist/chart.umd.min.js',
array(),
'4.4.1',
true
);
$script_deps[] = 'chartjs';
}
wp_enqueue_script(
'wp-bnb-admin',
WP_BNB_URL . 'assets/js/admin.js',
$script_deps,
WP_BNB_VERSION,
true
);
// Build localize data.
$localize_data = array(
'ajaxUrl' => admin_url( 'admin-ajax.php' ),
'nonce' => wp_create_nonce( 'wp_bnb_admin_nonce' ),
'postType' => $post_type,
'isDashboard' => $is_dashboard,
'i18n' => array(
'validating' => __( 'Validating...', 'wp-bnb' ),
'activating' => __( 'Activating...', 'wp-bnb' ),
'error' => __( 'An error occurred. Please try again.', 'wp-bnb' ),
'selectImages' => __( 'Select Images', 'wp-bnb' ),
'addToGallery' => __( 'Add to Gallery', 'wp-bnb' ),
'confirmRemove' => __( 'Are you sure you want to remove this image?', 'wp-bnb' ),
'increase' => __( 'increase', 'wp-bnb' ),
'discount' => __( 'discount', 'wp-bnb' ),
'normalPrice' => __( 'Normal price', 'wp-bnb' ),
'checking' => __( 'Checking availability...', 'wp-bnb' ),
'available' => __( 'Available', 'wp-bnb' ),
'notAvailable' => __( 'Not available - conflicts with existing booking', 'wp-bnb' ),
'selectRoomAndDates' => __( 'Select room and dates to check availability', 'wp-bnb' ),
'nights' => __( 'nights', 'wp-bnb' ),
'night' => __( 'night', 'wp-bnb' ),
'calculating' => __( 'Calculating price...', 'wp-bnb' ),
'searchingGuests' => __( 'Searching...', 'wp-bnb' ),
'noGuestsFound' => __( 'No guests found', 'wp-bnb' ),
'selectGuest' => __( 'Select', 'wp-bnb' ),
'guestBlocked' => __( 'Blocked', 'wp-bnb' ),
'perNightDescription' => __( 'This price will be charged per night of the stay.', 'wp-bnb' ),
'perBookingDescription' => __( 'This price will be charged once for the booking.', 'wp-bnb' ),
'justNow' => __( 'Just now', 'wp-bnb' ),
'updateAvailable' => __( 'Update available!', 'wp-bnb' ),
'upToDate' => __( '(You are up to date)', 'wp-bnb' ),
'checkingUpdates' => __( 'Checking for updates...', 'wp-bnb' ),
'occupancy' => __( 'Occupancy %', 'wp-bnb' ),
'revenue' => __( 'Revenue', 'wp-bnb' ),
),
);
// Add chart data for dashboard.
if ( $is_dashboard ) {
$localize_data['chartData'] = array(
'occupancy' => DashboardAdmin::get_occupancy_trend_data( 30 ),
'revenue' => DashboardAdmin::get_revenue_trend_data( 6 ),
);
}
wp_localize_script( 'wp-bnb-admin', 'wpBnbAdmin', $localize_data );
}
/**
* Enqueue frontend assets.
*
* @return void
*/
public function enqueue_frontend_assets(): void {
// Only load if licensed.
if ( ! LicenseManager::is_license_valid() ) {
return;
}
wp_enqueue_style(
'wp-bnb-frontend',
WP_BNB_URL . 'assets/css/frontend.css',
array(),
WP_BNB_VERSION
);
wp_enqueue_script(
'wp-bnb-frontend',
WP_BNB_URL . 'assets/js/frontend.js',
array(),
WP_BNB_VERSION,
true
);
wp_localize_script(
'wp-bnb-frontend',
'wpBnbFrontend',
array(
'ajaxUrl' => admin_url( 'admin-ajax.php' ),
'nonce' => wp_create_nonce( 'wp_bnb_frontend_nonce' ),
'i18n' => array(
'searching' => __( 'Searching...', 'wp-bnb' ),
'noResults' => __( 'No rooms found matching your criteria.', 'wp-bnb' ),
'resultsFound' => __( '%d rooms found', 'wp-bnb' ),
'loadMore' => __( 'Load More', 'wp-bnb' ),
'viewDetails' => __( 'View Details', 'wp-bnb' ),
'perNight' => __( 'night', 'wp-bnb' ),
'guests' => __( 'guests', 'wp-bnb' ),
'invalidDateRange' => __( 'Check-out must be after check-in', 'wp-bnb' ),
'selectDates' => __( 'Please select check-in and check-out dates.', 'wp-bnb' ),
'available' => __( 'Room is available!', 'wp-bnb' ),
'notAvailable' => __( 'Sorry, the room is not available for these dates.', 'wp-bnb' ),
'totalPrice' => __( 'Total', 'wp-bnb' ),
'bookNow' => __( 'Book Now', 'wp-bnb' ),
'total' => __( 'Total', 'wp-bnb' ),
'nights' => __( 'nights', 'wp-bnb' ),
'basePrice' => __( 'Base', 'wp-bnb' ),
'weekendSurcharge' => __( 'Weekend surcharge', 'wp-bnb' ),
'season' => __( 'Season', 'wp-bnb' ),
),
)
);
// Load CF7 integration assets if CF7 is active.
if ( class_exists( 'WPCF7' ) ) {
wp_enqueue_style(
'wp-bnb-cf7',
WP_BNB_URL . 'assets/css/cf7-integration.css',
array( 'contact-form-7' ),
WP_BNB_VERSION
);
wp_enqueue_script(
'wp-bnb-cf7',
WP_BNB_URL . 'assets/js/cf7-integration.js',
array( 'contact-form-7' ),
WP_BNB_VERSION,
true
);
wp_localize_script(
'wp-bnb-cf7',
'wpBnbCF7',
array(
'ajaxUrl' => admin_url( 'admin-ajax.php' ),
'nonce' => wp_create_nonce( 'wp_bnb_frontend_nonce' ),
'i18n' => array(
'selectRoom' => __( '-- Select Room --', 'wp-bnb' ),
'checking' => __( 'Checking availability...', 'wp-bnb' ),
'available' => __( 'Room is available!', 'wp-bnb' ),
'unavailable' => __( 'Room is not available for these dates', 'wp-bnb' ),
'invalidDateRange' => __( 'Check-out must be after check-in', 'wp-bnb' ),
'capacityExceeded' => __( 'Maximum %d guests for this room', 'wp-bnb' ),
'estimatedTotal' => __( 'Estimated Total', 'wp-bnb' ),
'nights' => __( 'nights', 'wp-bnb' ),
),
)
);
}
}
/**
* Add plugin action links.
*
* @param array $links Existing plugin links.
* @return array
*/
public function add_action_links( array $links ): array {
$plugin_links = array(
'<a href="' . esc_url( admin_url( 'admin.php?page=wp-bnb-settings' ) ) . '">' . esc_html__( 'Settings', 'wp-bnb' ) . '</a>',
);
return array_merge( $plugin_links, $links );
}
/**
* Register admin menu.
*
* @return void
*/
public function register_admin_menu(): void {
// Main menu.
add_menu_page(
__( 'WP BnB', 'wp-bnb' ),
__( 'WP BnB', 'wp-bnb' ),
'manage_options',
'wp-bnb',
array( $this, 'render_dashboard_page' ),
'dashicons-building',
30
);
// Dashboard submenu.
add_submenu_page(
'wp-bnb',
__( 'Dashboard', 'wp-bnb' ),
__( 'Dashboard', 'wp-bnb' ),
'manage_options',
'wp-bnb',
array( $this, 'render_dashboard_page' )
);
// Reports submenu.
add_submenu_page(
'wp-bnb',
__( 'Reports', 'wp-bnb' ),
__( 'Reports', 'wp-bnb' ),
'manage_options',
'wp-bnb-reports',
array( $this, 'render_reports_page' )
);
// Settings submenu.
add_submenu_page(
'wp-bnb',
__( 'Settings', 'wp-bnb' ),
__( 'Settings', 'wp-bnb' ),
'manage_options',
'wp-bnb-settings',
array( $this, 'render_settings_page' )
);
}
/**
* Reorder the admin submenu items.
*
* Places Dashboard at top, Settings at bottom, and organizes
* the remaining items in logical order.
*
* @return void
*/
public function reorder_admin_menu(): void {
global $submenu;
if ( ! isset( $submenu['wp-bnb'] ) ) {
return;
}
// Define the desired order of menu slugs.
$desired_order = array(
'wp-bnb', // Dashboard.
'edit.php?post_type=bnb_building', // Buildings.
'edit.php?post_type=bnb_room', // Rooms.
'edit.php?post_type=bnb_booking', // Bookings.
'edit.php?post_type=bnb_guest', // Guests.
'edit.php?post_type=bnb_service', // Services.
'wp-bnb-calendar', // Calendar.
'wp-bnb-reports', // Reports.
'wp-bnb-seasons', // Seasons.
'wp-bnb-settings', // Settings (always last).
);
$current_menu = $submenu['wp-bnb'];
$ordered_menu = array();
$index = 0;
// Add items in the desired order.
foreach ( $desired_order as $slug ) {
foreach ( $current_menu as $key => $item ) {
if ( $item[2] === $slug ) {
$ordered_menu[ $index ] = $item;
unset( $current_menu[ $key ] );
++$index;
break;
}
}
}
// Append any remaining items not in the desired order.
foreach ( $current_menu as $item ) {
$ordered_menu[ $index ] = $item;
++$index;
}
$submenu['wp-bnb'] = $ordered_menu;
}
/**
* Register plugin settings.
*
* @return void
*/
public function register_settings(): void {
// License settings are handled by LicenseManager.
// Additional settings will be added here.
}
/**
* Render dashboard page.
*
* @return void
*/
public function render_dashboard_page(): void {
DashboardAdmin::render();
}
/**
* Render reports page.
*
* @return void
*/
public function render_reports_page(): void {
ReportsAdmin::render();
}
/**
* Render settings page.
*
* @return void
*/
public function render_settings_page(): void {
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Tab switching only.
$active_tab = isset( $_GET['tab'] ) ? sanitize_key( $_GET['tab'] ) : 'general';
// Handle form submission.
if ( isset( $_POST['wp_bnb_settings_nonce'] ) && wp_verify_nonce( sanitize_key( $_POST['wp_bnb_settings_nonce'] ), 'wp_bnb_save_settings' ) ) {
$this->save_settings( $active_tab );
}
?>
<div class="wrap">
<h1><?php esc_html_e( 'WP BnB Settings', 'wp-bnb' ); ?></h1>
<nav class="nav-tab-wrapper">
<a href="<?php echo esc_url( admin_url( 'admin.php?page=wp-bnb-settings&tab=general' ) ); ?>"
class="nav-tab <?php echo 'general' === $active_tab ? 'nav-tab-active' : ''; ?>">
<?php esc_html_e( 'General', 'wp-bnb' ); ?>
</a>
<a href="<?php echo esc_url( admin_url( 'admin.php?page=wp-bnb-settings&tab=pricing' ) ); ?>"
class="nav-tab <?php echo 'pricing' === $active_tab ? 'nav-tab-active' : ''; ?>">
<?php esc_html_e( 'Pricing', 'wp-bnb' ); ?>
</a>
<a href="<?php echo esc_url( admin_url( 'admin.php?page=wp-bnb-settings&tab=license' ) ); ?>"
class="nav-tab <?php echo 'license' === $active_tab ? 'nav-tab-active' : ''; ?>">
<?php esc_html_e( 'License', 'wp-bnb' ); ?>
</a>
<a href="<?php echo esc_url( admin_url( 'admin.php?page=wp-bnb-settings&tab=updates' ) ); ?>"
class="nav-tab <?php echo 'updates' === $active_tab ? 'nav-tab-active' : ''; ?>">
<?php esc_html_e( 'Updates', 'wp-bnb' ); ?>
</a>
<a href="<?php echo esc_url( admin_url( 'admin.php?page=wp-bnb-settings&tab=metrics' ) ); ?>"
class="nav-tab <?php echo 'metrics' === $active_tab ? 'nav-tab-active' : ''; ?>">
<?php esc_html_e( 'Metrics', 'wp-bnb' ); ?>
</a>
<a href="<?php echo esc_url( admin_url( 'admin.php?page=wp-bnb-settings&tab=api' ) ); ?>"
class="nav-tab <?php echo 'api' === $active_tab ? 'nav-tab-active' : ''; ?>">
<?php esc_html_e( 'API', 'wp-bnb' ); ?>
</a>
<a href="<?php echo esc_url( admin_url( 'admin.php?page=wp-bnb-settings&tab=woocommerce' ) ); ?>"
class="nav-tab <?php echo 'woocommerce' === $active_tab ? 'nav-tab-active' : ''; ?>">
<?php esc_html_e( 'WooCommerce', 'wp-bnb' ); ?>
</a>
</nav>
<div class="tab-content">
<?php
switch ( $active_tab ) {
case 'pricing':
$this->render_pricing_settings();
break;
case 'license':
$this->render_license_settings();
break;
case 'updates':
$this->render_updates_settings();
break;
case 'metrics':
$this->render_metrics_settings();
break;
case 'api':
$this->render_api_settings();
break;
case 'woocommerce':
$this->render_woocommerce_settings();
break;
default:
$this->render_general_settings();
break;
}
?>
</div>
</div>
<?php
}
/**
* Render general settings tab.
*
* @return void
*/
private function render_general_settings(): void {
?>
<form method="post" action="">
<?php wp_nonce_field( 'wp_bnb_save_settings', 'wp_bnb_settings_nonce' ); ?>
<h2><?php esc_html_e( 'Business Information', 'wp-bnb' ); ?></h2>
<table class="form-table" role="presentation">
<tr>
<th scope="row">
<label for="wp_bnb_business_name"><?php esc_html_e( 'Business Name', 'wp-bnb' ); ?></label>
</th>
<td>
<input type="text" name="wp_bnb_business_name" id="wp_bnb_business_name"
value="<?php echo esc_attr( get_option( 'wp_bnb_business_name', '' ) ); ?>"
class="regular-text">
<p class="description"><?php esc_html_e( 'The name of your B&B business.', 'wp-bnb' ); ?></p>
</td>
</tr>
<tr>
<th scope="row">
<label for="wp_bnb_currency"><?php esc_html_e( 'Currency', 'wp-bnb' ); ?></label>
</th>
<td>
<select name="wp_bnb_currency" id="wp_bnb_currency">
<?php
$currencies = array(
'CHF' => __( 'Swiss Franc (CHF)', 'wp-bnb' ),
'EUR' => __( 'Euro (EUR)', 'wp-bnb' ),
'USD' => __( 'US Dollar (USD)', 'wp-bnb' ),
'GBP' => __( 'British Pound (GBP)', 'wp-bnb' ),
);
$current = get_option( 'wp_bnb_currency', 'CHF' );
foreach ( $currencies as $code => $label ) :
?>
<option value="<?php echo esc_attr( $code ); ?>" <?php selected( $current, $code ); ?>>
<?php echo esc_html( $label ); ?>
</option>
<?php endforeach; ?>
</select>
</td>
</tr>
</table>
<h2><?php esc_html_e( 'Address', 'wp-bnb' ); ?></h2>
<table class="form-table" role="presentation">
<tr>
<th scope="row">
<label for="wp_bnb_address_street"><?php esc_html_e( 'Street Address', 'wp-bnb' ); ?></label>
</th>
<td>
<input type="text" name="wp_bnb_address_street" id="wp_bnb_address_street"
value="<?php echo esc_attr( get_option( 'wp_bnb_address_street', '' ) ); ?>"
class="regular-text">
</td>
</tr>
<tr>
<th scope="row">
<label for="wp_bnb_address_city"><?php esc_html_e( 'City', 'wp-bnb' ); ?></label>
</th>
<td>
<input type="text" name="wp_bnb_address_city" id="wp_bnb_address_city"
value="<?php echo esc_attr( get_option( 'wp_bnb_address_city', '' ) ); ?>"
class="regular-text">
</td>
</tr>
<tr>
<th scope="row">
<label for="wp_bnb_address_postal"><?php esc_html_e( 'Postal Code', 'wp-bnb' ); ?></label>
</th>
<td>
<input type="text" name="wp_bnb_address_postal" id="wp_bnb_address_postal"
value="<?php echo esc_attr( get_option( 'wp_bnb_address_postal', '' ) ); ?>"
class="small-text">
</td>
</tr>
<tr>
<th scope="row">
<label for="wp_bnb_address_country"><?php esc_html_e( 'Country', 'wp-bnb' ); ?></label>
</th>
<td>
<input type="text" name="wp_bnb_address_country" id="wp_bnb_address_country"
value="<?php echo esc_attr( get_option( 'wp_bnb_address_country', '' ) ); ?>"
class="regular-text">
</td>
</tr>
</table>
<h2><?php esc_html_e( 'Contact Information', 'wp-bnb' ); ?></h2>
<table class="form-table" role="presentation">
<tr>
<th scope="row">
<label for="wp_bnb_contact_email"><?php esc_html_e( 'Email Address', 'wp-bnb' ); ?></label>
</th>
<td>
<input type="email" name="wp_bnb_contact_email" id="wp_bnb_contact_email"
value="<?php echo esc_attr( get_option( 'wp_bnb_contact_email', '' ) ); ?>"
class="regular-text">
<p class="description"><?php esc_html_e( 'Primary contact email for bookings and inquiries.', 'wp-bnb' ); ?></p>
</td>
</tr>
<tr>
<th scope="row">
<label for="wp_bnb_contact_phone"><?php esc_html_e( 'Phone Number', 'wp-bnb' ); ?></label>
</th>
<td>
<input type="tel" name="wp_bnb_contact_phone" id="wp_bnb_contact_phone"
value="<?php echo esc_attr( get_option( 'wp_bnb_contact_phone', '' ) ); ?>"
class="regular-text" placeholder="+41 12 345 67 89">
</td>
</tr>
<tr>
<th scope="row">
<label for="wp_bnb_contact_website"><?php esc_html_e( 'Website', 'wp-bnb' ); ?></label>
</th>
<td>
<input type="url" name="wp_bnb_contact_website" id="wp_bnb_contact_website"
value="<?php echo esc_attr( get_option( 'wp_bnb_contact_website', '' ) ); ?>"
class="regular-text" placeholder="https://">
</td>
</tr>
</table>
<h2><?php esc_html_e( 'Social Media', 'wp-bnb' ); ?></h2>
<table class="form-table" role="presentation">
<tr>
<th scope="row">
<label for="wp_bnb_social_facebook"><?php esc_html_e( 'Facebook', 'wp-bnb' ); ?></label>
</th>
<td>
<input type="url" name="wp_bnb_social_facebook" id="wp_bnb_social_facebook"
value="<?php echo esc_attr( get_option( 'wp_bnb_social_facebook', '' ) ); ?>"
class="regular-text" placeholder="https://facebook.com/yourpage">
</td>
</tr>
<tr>
<th scope="row">
<label for="wp_bnb_social_instagram"><?php esc_html_e( 'Instagram', 'wp-bnb' ); ?></label>
</th>
<td>
<input type="url" name="wp_bnb_social_instagram" id="wp_bnb_social_instagram"
value="<?php echo esc_attr( get_option( 'wp_bnb_social_instagram', '' ) ); ?>"
class="regular-text" placeholder="https://instagram.com/yourprofile">
</td>
</tr>
<tr>
<th scope="row">
<label for="wp_bnb_social_x"><?php esc_html_e( 'X (Twitter)', 'wp-bnb' ); ?></label>
</th>
<td>
<input type="url" name="wp_bnb_social_x" id="wp_bnb_social_x"
value="<?php echo esc_attr( get_option( 'wp_bnb_social_x', '' ) ); ?>"
class="regular-text" placeholder="https://x.com/yourhandle">
</td>
</tr>
<tr>
<th scope="row">
<label for="wp_bnb_social_linkedin"><?php esc_html_e( 'LinkedIn', 'wp-bnb' ); ?></label>
</th>
<td>
<input type="url" name="wp_bnb_social_linkedin" id="wp_bnb_social_linkedin"
value="<?php echo esc_attr( get_option( 'wp_bnb_social_linkedin', '' ) ); ?>"
class="regular-text" placeholder="https://linkedin.com/company/yourcompany">
</td>
</tr>
<tr>
<th scope="row">
<label for="wp_bnb_social_tripadvisor"><?php esc_html_e( 'TripAdvisor', 'wp-bnb' ); ?></label>
</th>
<td>
<input type="url" name="wp_bnb_social_tripadvisor" id="wp_bnb_social_tripadvisor"
value="<?php echo esc_attr( get_option( 'wp_bnb_social_tripadvisor', '' ) ); ?>"
class="regular-text" placeholder="https://tripadvisor.com/...">
<p class="description"><?php esc_html_e( 'Link to your TripAdvisor listing.', 'wp-bnb' ); ?></p>
</td>
</tr>
</table>
<?php submit_button( __( 'Save Settings', 'wp-bnb' ) ); ?>
</form>
<?php
}
/**
* Render pricing settings tab.
*
* @return void
*/
private function render_pricing_settings(): void {
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Subtab switching only.
$active_subtab = isset( $_GET['subtab'] ) ? sanitize_key( $_GET['subtab'] ) : 'tiers';
$short_term_max = get_option( 'wp_bnb_short_term_max_nights', 6 );
$mid_term_max = get_option( 'wp_bnb_mid_term_max_nights', 27 );
$weekend_days = get_option( 'wp_bnb_weekend_days', '5,6' );
$seasons = Season::all();
$days_of_week = array(
1 => __( 'Monday', 'wp-bnb' ),
2 => __( 'Tuesday', 'wp-bnb' ),
3 => __( 'Wednesday', 'wp-bnb' ),
4 => __( 'Thursday', 'wp-bnb' ),
5 => __( 'Friday', 'wp-bnb' ),
6 => __( 'Saturday', 'wp-bnb' ),
7 => __( 'Sunday', 'wp-bnb' ),
);
$selected_days = array_map( 'intval', explode( ',', $weekend_days ) );
$base_url = admin_url( 'admin.php?page=wp-bnb-settings&tab=pricing' );
?>
<!-- Pricing Subtabs -->
<div class="wp-bnb-subtabs">
<a href="<?php echo esc_url( $base_url . '&subtab=tiers' ); ?>"
class="wp-bnb-subtab <?php echo 'tiers' === $active_subtab ? 'active' : ''; ?>">
<span class="dashicons dashicons-chart-bar"></span>
<?php esc_html_e( 'Pricing Tiers', 'wp-bnb' ); ?>
</a>
<a href="<?php echo esc_url( $base_url . '&subtab=weekend' ); ?>"
class="wp-bnb-subtab <?php echo 'weekend' === $active_subtab ? 'active' : ''; ?>">
<span class="dashicons dashicons-calendar-alt"></span>
<?php esc_html_e( 'Weekend Days', 'wp-bnb' ); ?>
</a>
<a href="<?php echo esc_url( $base_url . '&subtab=seasons' ); ?>"
class="wp-bnb-subtab <?php echo 'seasons' === $active_subtab ? 'active' : ''; ?>">
<span class="dashicons dashicons-image-filter"></span>
<?php esc_html_e( 'Seasons', 'wp-bnb' ); ?>
</a>
</div>
<form method="post" action="">
<?php wp_nonce_field( 'wp_bnb_save_settings', 'wp_bnb_settings_nonce' ); ?>
<?php if ( 'tiers' === $active_subtab ) : ?>
<!-- Pricing Tiers Subtab -->
<h2><?php esc_html_e( 'Pricing Tier Thresholds', 'wp-bnb' ); ?></h2>
<p class="description"><?php esc_html_e( 'Define the number of nights that determine which pricing tier applies.', 'wp-bnb' ); ?></p>
<table class="form-table" role="presentation">
<tr>
<th scope="row">
<label for="wp_bnb_short_term_max_nights"><?php esc_html_e( 'Short-term (Nightly)', 'wp-bnb' ); ?></label>
</th>
<td>
<input type="number" name="wp_bnb_short_term_max_nights" id="wp_bnb_short_term_max_nights"
value="<?php echo esc_attr( $short_term_max ); ?>"
class="small-text" min="1" max="30">
<?php esc_html_e( 'nights or fewer', 'wp-bnb' ); ?>
<p class="description"><?php esc_html_e( 'Stays up to this many nights use the nightly rate.', 'wp-bnb' ); ?></p>
</td>
</tr>
<tr>
<th scope="row">
<label for="wp_bnb_mid_term_max_nights"><?php esc_html_e( 'Mid-term (Weekly)', 'wp-bnb' ); ?></label>
</th>
<td>
<input type="number" name="wp_bnb_mid_term_max_nights" id="wp_bnb_mid_term_max_nights"
value="<?php echo esc_attr( $mid_term_max ); ?>"
class="small-text" min="7" max="90">
<?php esc_html_e( 'nights or fewer', 'wp-bnb' ); ?>
<p class="description"><?php esc_html_e( 'Stays longer than short-term but up to this many nights use the weekly rate.', 'wp-bnb' ); ?></p>
</td>
</tr>
<tr>
<th scope="row"><?php esc_html_e( 'Long-term (Monthly)', 'wp-bnb' ); ?></th>
<td>
<p class="description">
<?php
printf(
/* translators: %s: number of nights */
esc_html__( 'Stays longer than %s nights use the monthly rate.', 'wp-bnb' ),
'<strong id="wp-bnb-long-term-min">' . esc_html( $mid_term_max ) . '</strong>'
);
?>
</p>
</td>
</tr>
</table>
<?php submit_button( __( 'Save Pricing Tiers', 'wp-bnb' ) ); ?>
<?php elseif ( 'weekend' === $active_subtab ) : ?>
<!-- Weekend Days Subtab -->
<h2><?php esc_html_e( 'Weekend Days', 'wp-bnb' ); ?></h2>
<p class="description"><?php esc_html_e( 'Select which days are considered weekend days for weekend surcharges.', 'wp-bnb' ); ?></p>
<table class="form-table" role="presentation">
<tr>
<th scope="row"><?php esc_html_e( 'Weekend Days', 'wp-bnb' ); ?></th>
<td>
<fieldset>
<?php foreach ( $days_of_week as $day_num => $day_name ) : ?>
<label style="display: inline-block; margin-right: 15px;">
<input type="checkbox" name="wp_bnb_weekend_days[]" value="<?php echo esc_attr( $day_num ); ?>"
<?php checked( in_array( $day_num, $selected_days, true ) ); ?>>
<?php echo esc_html( $day_name ); ?>
</label>
<?php endforeach; ?>
</fieldset>
<p class="description"><?php esc_html_e( 'Weekend surcharges (configured per room) apply to nights starting on these days.', 'wp-bnb' ); ?></p>
</td>
</tr>
</table>
<?php submit_button( __( 'Save Weekend Days', 'wp-bnb' ) ); ?>
<?php else : ?>
<!-- Seasons Subtab -->
<h2><?php esc_html_e( 'Seasonal Pricing', 'wp-bnb' ); ?></h2>
<p class="description">
<?php esc_html_e( 'Seasonal pricing allows you to adjust room rates based on time of year. Prices are multiplied by the season modifier.', 'wp-bnb' ); ?>
</p>
<p>
<a href="<?php echo esc_url( admin_url( 'admin.php?page=wp-bnb-seasons' ) ); ?>" class="button button-primary">
<span class="dashicons dashicons-admin-settings" style="vertical-align: text-top;"></span>
<?php esc_html_e( 'Manage Seasons', 'wp-bnb' ); ?>
</a>
</p>
<?php if ( ! empty( $seasons ) ) : ?>
<table class="widefat striped" style="max-width: 700px; margin-top: 20px;">
<thead>
<tr>
<th><?php esc_html_e( 'Season', 'wp-bnb' ); ?></th>
<th><?php esc_html_e( 'Period', 'wp-bnb' ); ?></th>
<th><?php esc_html_e( 'Modifier', 'wp-bnb' ); ?></th>
<th><?php esc_html_e( 'Priority', 'wp-bnb' ); ?></th>
<th><?php esc_html_e( 'Status', 'wp-bnb' ); ?></th>
</tr>
</thead>
<tbody>
<?php foreach ( $seasons as $season ) : ?>
<tr>
<td><strong><?php echo esc_html( $season->name ); ?></strong></td>
<td><?php echo esc_html( $season->start_date . ' - ' . $season->end_date ); ?></td>
<td><?php echo esc_html( $season->getModifierLabel() ); ?></td>
<td><?php echo esc_html( $season->priority ); ?></td>
<td>
<?php if ( $season->active ) : ?>
<span class="bnb-status-active"><?php esc_html_e( 'Active', 'wp-bnb' ); ?></span>
<?php else : ?>
<span class="bnb-status-inactive"><?php esc_html_e( 'Inactive', 'wp-bnb' ); ?></span>
<?php endif; ?>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<?php else : ?>
<div class="notice notice-info inline" style="margin: 20px 0;">
<p><?php esc_html_e( 'No seasons configured yet. Create seasons to apply price modifiers during specific periods.', 'wp-bnb' ); ?></p>
</div>
<?php endif; ?>
<?php endif; ?>
</form>
<?php
}
/**
* Render license settings tab.
*
* @return void
*/
private function render_license_settings(): void {
$license_key = LicenseManager::get_license_key();
$server_url = LicenseManager::get_server_url();
$license_status = LicenseManager::get_cached_status();
$license_data = LicenseManager::get_cached_data();
$last_check = LicenseManager::get_last_check();
$is_localhost = LicenseManager::is_localhost();
?>
<form method="post" action="">
<?php wp_nonce_field( 'wp_bnb_save_settings', 'wp_bnb_settings_nonce' ); ?>
<?php if ( $is_localhost ) : ?>
<div class="notice notice-info inline" style="margin: 0 0 20px 0;">
<p>
<span class="dashicons dashicons-info" style="color: #72aee6;"></span>
<strong><?php esc_html_e( 'Development Mode', 'wp-bnb' ); ?></strong>
<?php esc_html_e( 'You are running on a local development environment. License validation is bypassed and all features are enabled.', 'wp-bnb' ); ?>
</p>
</div>
<?php endif; ?>
<h2><?php esc_html_e( 'License Status', 'wp-bnb' ); ?></h2>
<table class="form-table" role="presentation">
<tr>
<th scope="row"><?php esc_html_e( 'Status', 'wp-bnb' ); ?></th>
<td>
<?php $this->render_license_status_badge( $license_status ); ?>
<?php if ( $last_check > 0 ) : ?>
<p class="description">
<?php
printf(
/* translators: %s: Time ago string */
esc_html__( 'Last checked: %s', 'wp-bnb' ),
esc_html( human_time_diff( $last_check, time() ) . ' ' . __( 'ago', 'wp-bnb' ) )
);
?>
</p>
<?php endif; ?>
</td>
</tr>
<?php if ( ! empty( $license_data['expires'] ) ) : ?>
<tr>
<th scope="row"><?php esc_html_e( 'Expires', 'wp-bnb' ); ?></th>
<td>
<?php echo esc_html( wp_date( get_option( 'date_format' ), strtotime( $license_data['expires'] ) ) ); ?>
</td>
</tr>
<?php endif; ?>
</table>
<h2><?php esc_html_e( 'License Configuration', 'wp-bnb' ); ?></h2>
<table class="form-table" role="presentation">
<tr>
<th scope="row">
<label for="wp_bnb_license_server_url"><?php esc_html_e( 'License Server URL', 'wp-bnb' ); ?></label>
</th>
<td>
<input type="url" name="wp_bnb_license_server_url" id="wp_bnb_license_server_url"
value="<?php echo esc_attr( $server_url ); ?>"
class="regular-text" placeholder="https://example.com">
<p class="description"><?php esc_html_e( 'The URL of the license server.', 'wp-bnb' ); ?></p>
</td>
</tr>
<tr>
<th scope="row">
<label for="wp_bnb_license_key"><?php esc_html_e( 'License Key', 'wp-bnb' ); ?></label>
</th>
<td>
<input type="text" name="wp_bnb_license_key" id="wp_bnb_license_key"
value="<?php echo esc_attr( $license_key ); ?>"
class="regular-text" placeholder="XXXX-XXXX-XXXX-XXXX">
<p class="description"><?php esc_html_e( 'Your license key from the purchase confirmation email.', 'wp-bnb' ); ?></p>
</td>
</tr>
<tr>
<th scope="row">
<label for="wp_bnb_license_server_secret"><?php esc_html_e( 'Server Secret', 'wp-bnb' ); ?></label>
</th>
<td>
<input type="password" name="wp_bnb_license_server_secret" id="wp_bnb_license_server_secret"
value="" class="regular-text"
placeholder="<?php echo ! empty( LicenseManager::get_server_secret() ) ? '••••••••••••••••' : ''; ?>">
<p class="description"><?php esc_html_e( 'Leave empty to keep the current secret. The shared secret for secure communication.', 'wp-bnb' ); ?></p>
</td>
</tr>
</table>
<p class="submit">
<?php submit_button( __( 'Save License Settings', 'wp-bnb' ), 'primary', 'submit', false ); ?>
<button type="button" id="wp-bnb-validate-license" class="button button-secondary">
<?php esc_html_e( 'Validate License', 'wp-bnb' ); ?>
</button>
<button type="button" id="wp-bnb-activate-license" class="button button-secondary">
<?php esc_html_e( 'Activate License', 'wp-bnb' ); ?>
</button>
<span class="spinner" id="wp-bnb-license-spinner"></span>
</p>
<div id="wp-bnb-license-message" style="display: none;"></div>
</form>
<?php
}
/**
* Render updates settings tab.
*
* @return void
*/
private function render_updates_settings(): void {
$updater = LicenseUpdater::get_instance();
if ( null === $updater ) {
?>
<p><?php esc_html_e( 'Update checker is not available.', 'wp-bnb' ); ?></p>
<?php
return;
}
$current_version = $updater->get_current_version();
$last_check = LicenseUpdater::get_last_check();
$update_info = $updater->get_cached_update_info();
$update_available = false;
$latest_version = $current_version;
$notifications_enabled = LicenseUpdater::is_notifications_enabled();
$auto_install_enabled = LicenseUpdater::is_auto_install_enabled();
$check_frequency = LicenseUpdater::get_check_frequency();
$license_valid = LicenseManager::is_license_valid();
if ( $update_info instanceof UpdateInfo && $update_info->updateAvailable ) {
$latest_version = $update_info->version ?? $current_version;
$update_available = version_compare( $current_version, $latest_version, '<' );
}
?>
<form method="post" action="">
<?php wp_nonce_field( 'wp_bnb_save_settings', 'wp_bnb_settings_nonce' ); ?>
<h2><?php esc_html_e( 'Update Status', 'wp-bnb' ); ?></h2>
<table class="form-table" role="presentation">
<tr>
<th scope="row"><?php esc_html_e( 'Current Version', 'wp-bnb' ); ?></th>
<td>
<strong><?php echo esc_html( $current_version ); ?></strong>
</td>
</tr>
<tr>
<th scope="row"><?php esc_html_e( 'Latest Version', 'wp-bnb' ); ?></th>
<td>
<span id="wp-bnb-latest-version">
<?php if ( $update_available ) : ?>
<span style="color: #00a32a; font-weight: 600;">
<?php echo esc_html( $latest_version ); ?>
</span>
<span class="dashicons dashicons-yes" style="color: #00a32a;"></span>
<em><?php esc_html_e( 'Update available!', 'wp-bnb' ); ?></em>
<?php else : ?>
<?php echo esc_html( $latest_version ); ?>
<span style="color: #646970;">
<?php esc_html_e( '(You are up to date)', 'wp-bnb' ); ?>
</span>
<?php endif; ?>
</span>
</td>
</tr>
<tr>
<th scope="row"><?php esc_html_e( 'Last Check', 'wp-bnb' ); ?></th>
<td>
<span id="wp-bnb-update-last-check">
<?php if ( $last_check > 0 ) : ?>
<?php
printf(
/* translators: %s: Time ago string */
esc_html__( '%s ago', 'wp-bnb' ),
esc_html( human_time_diff( $last_check, time() ) )
);
?>
<?php else : ?>
<?php esc_html_e( 'Never', 'wp-bnb' ); ?>
<?php endif; ?>
</span>
</td>
</tr>
</table>
<p style="margin-bottom: 30px;">
<button type="button" id="wp-bnb-check-updates" class="button button-secondary">
<span class="dashicons dashicons-update" style="vertical-align: text-top;"></span>
<?php esc_html_e( 'Check for Updates', 'wp-bnb' ); ?>
</button>
<span class="spinner" id="wp-bnb-update-spinner"></span>
<?php if ( $update_available ) : ?>
<a href="<?php echo esc_url( admin_url( 'plugins.php' ) ); ?>" class="button button-primary">
<?php esc_html_e( 'Go to Plugins Page', 'wp-bnb' ); ?>
</a>
<?php endif; ?>
</p>
<div id="wp-bnb-update-message" style="display: none; margin-bottom: 20px;"></div>
<h2><?php esc_html_e( 'Update Settings', 'wp-bnb' ); ?></h2>
<table class="form-table" role="presentation">
<tr>
<th scope="row"><?php esc_html_e( 'Enable Update Notifications', 'wp-bnb' ); ?></th>
<td>
<label>
<input type="checkbox" name="wp_bnb_update_notifications_enabled"
value="yes" <?php checked( $notifications_enabled ); ?>>
<?php esc_html_e( 'Check for updates from the license server', 'wp-bnb' ); ?>
</label>
<p class="description">
<?php esc_html_e( 'When enabled, the plugin will check for updates and show notifications in the WordPress admin.', 'wp-bnb' ); ?>
</p>
</td>
</tr>
<tr>
<th scope="row"><?php esc_html_e( 'Automatic Updates', 'wp-bnb' ); ?></th>
<td>
<label>
<input type="checkbox" name="wp_bnb_auto_install_enabled"
value="yes" <?php checked( $auto_install_enabled ); ?>
<?php disabled( ! $license_valid ); ?>>
<?php esc_html_e( 'Automatically install updates', 'wp-bnb' ); ?>
</label>
<?php if ( ! $license_valid ) : ?>
<span style="color: #d63638; margin-left: 10px;">
<?php esc_html_e( '(Requires valid license)', 'wp-bnb' ); ?>
</span>
<?php endif; ?>
<p class="description">
<?php esc_html_e( 'When enabled, updates will be automatically installed during WordPress background updates.', 'wp-bnb' ); ?>
</p>
</td>
</tr>
<tr>
<th scope="row">
<label for="wp_bnb_update_check_frequency"><?php esc_html_e( 'Check Frequency', 'wp-bnb' ); ?></label>
</th>
<td>
<input type="number" name="wp_bnb_update_check_frequency" id="wp_bnb_update_check_frequency"
value="<?php echo esc_attr( $check_frequency ); ?>"
min="1" max="168" class="small-text">
<?php esc_html_e( 'hours', 'wp-bnb' ); ?>
<p class="description">
<?php esc_html_e( 'How often to check for updates (1-168 hours). Default: 12 hours.', 'wp-bnb' ); ?>
</p>
</td>
</tr>
</table>
<p class="submit">
<?php submit_button( __( 'Save Update Settings', 'wp-bnb' ), 'primary', 'submit', false ); ?>
</p>
</form>
<?php
}
/**
* Render metrics settings tab.
*
* @return void
*/
private function render_metrics_settings(): void {
$metrics_enabled = Prometheus::is_enabled();
$prometheus_active = class_exists( '\Magdev\WpPrometheus\Plugin' ) || defined( 'WP_PROMETHEUS_VERSION' );
$dashboard_file = WP_BNB_PATH . 'assets/grafana/wp-bnb-dashboard.json';
$dashboard_available = file_exists( $dashboard_file );
?>
<form method="post" action="">
<?php wp_nonce_field( 'wp_bnb_save_settings', 'wp_bnb_settings_nonce' ); ?>
<h2><?php esc_html_e( 'Prometheus Metrics', 'wp-bnb' ); ?></h2>
<?php if ( ! $prometheus_active ) : ?>
<div class="notice notice-warning inline" style="margin: 0 0 20px 0;">
<p>
<span class="dashicons dashicons-warning" style="color: #dba617;"></span>
<strong><?php esc_html_e( 'WP Prometheus not detected', 'wp-bnb' ); ?></strong><br>
<?php
printf(
/* translators: %s: Plugin URL */
esc_html__( 'The WP Prometheus plugin is required to expose metrics. Please install and activate it from %s.', 'wp-bnb' ),
'<a href="https://src.bundespruefstelle.ch/magdev/wp-prometheus" target="_blank">wp-prometheus</a>'
);
?>
</p>
</div>
<?php else : ?>
<div class="notice notice-success inline" style="margin: 0 0 20px 0;">
<p>
<span class="dashicons dashicons-yes-alt" style="color: #00a32a;"></span>
<strong><?php esc_html_e( 'WP Prometheus is active', 'wp-bnb' ); ?></strong>
<?php esc_html_e( 'Metrics will be exposed via the /metrics/ endpoint.', 'wp-bnb' ); ?>
</p>
</div>
<?php endif; ?>
<table class="form-table" role="presentation">
<tr>
<th scope="row"><?php esc_html_e( 'Enable Metrics', 'wp-bnb' ); ?></th>
<td>
<label>
<input type="checkbox" name="wp_bnb_metrics_enabled"
value="yes" <?php checked( $metrics_enabled ); ?>>
<?php esc_html_e( 'Expose BnB metrics via Prometheus', 'wp-bnb' ); ?>
</label>
<p class="description">
<?php esc_html_e( 'When enabled, occupancy, booking, revenue, and guest metrics will be available for Prometheus scraping.', 'wp-bnb' ); ?>
</p>
</td>
</tr>
</table>
<h2><?php esc_html_e( 'Available Metrics', 'wp-bnb' ); ?></h2>
<p class="description"><?php esc_html_e( 'The following metrics are exposed when enabled:', 'wp-bnb' ); ?></p>
<table class="widefat striped" style="max-width: 800px; margin-top: 10px;">
<thead>
<tr>
<th><?php esc_html_e( 'Metric', 'wp-bnb' ); ?></th>
<th><?php esc_html_e( 'Type', 'wp-bnb' ); ?></th>
<th><?php esc_html_e( 'Description', 'wp-bnb' ); ?></th>
</tr>
</thead>
<tbody>
<tr>
<td><code>wp_bnb_buildings_total</code></td>
<td><?php esc_html_e( 'Gauge', 'wp-bnb' ); ?></td>
<td><?php esc_html_e( 'Total number of buildings', 'wp-bnb' ); ?></td>
</tr>
<tr>
<td><code>wp_bnb_rooms_total</code></td>
<td><?php esc_html_e( 'Gauge', 'wp-bnb' ); ?></td>
<td><?php esc_html_e( 'Total rooms by status (available, occupied, maintenance, inactive)', 'wp-bnb' ); ?></td>
</tr>
<tr>
<td><code>wp_bnb_bookings_total</code></td>
<td><?php esc_html_e( 'Gauge', 'wp-bnb' ); ?></td>
<td><?php esc_html_e( 'Total bookings by status (pending, confirmed, checked_in, checked_out, cancelled)', 'wp-bnb' ); ?></td>
</tr>
<tr>
<td><code>wp_bnb_occupancy_rate_current</code></td>
<td><?php esc_html_e( 'Gauge', 'wp-bnb' ); ?></td>
<td><?php esc_html_e( 'Current room occupancy rate (percentage)', 'wp-bnb' ); ?></td>
</tr>
<tr>
<td><code>wp_bnb_occupancy_rate_this_month</code></td>
<td><?php esc_html_e( 'Gauge', 'wp-bnb' ); ?></td>
<td><?php esc_html_e( 'Room occupancy rate for current month (percentage)', 'wp-bnb' ); ?></td>
</tr>
<tr>
<td><code>wp_bnb_checkins_today</code></td>
<td><?php esc_html_e( 'Gauge', 'wp-bnb' ); ?></td>
<td><?php esc_html_e( 'Number of check-ins scheduled for today', 'wp-bnb' ); ?></td>
</tr>
<tr>
<td><code>wp_bnb_checkouts_today</code></td>
<td><?php esc_html_e( 'Gauge', 'wp-bnb' ); ?></td>
<td><?php esc_html_e( 'Number of check-outs scheduled for today', 'wp-bnb' ); ?></td>
</tr>
<tr>
<td><code>wp_bnb_revenue_this_month</code></td>
<td><?php esc_html_e( 'Gauge', 'wp-bnb' ); ?></td>
<td><?php esc_html_e( 'Total revenue for current month', 'wp-bnb' ); ?></td>
</tr>
<tr>
<td><code>wp_bnb_revenue_ytd</code></td>
<td><?php esc_html_e( 'Gauge', 'wp-bnb' ); ?></td>
<td><?php esc_html_e( 'Total revenue year to date', 'wp-bnb' ); ?></td>
</tr>
<tr>
<td><code>wp_bnb_booking_avg_value</code></td>
<td><?php esc_html_e( 'Gauge', 'wp-bnb' ); ?></td>
<td><?php esc_html_e( 'Average booking value', 'wp-bnb' ); ?></td>
</tr>
<tr>
<td><code>wp_bnb_guests_total</code></td>
<td><?php esc_html_e( 'Gauge', 'wp-bnb' ); ?></td>
<td><?php esc_html_e( 'Total number of registered guests', 'wp-bnb' ); ?></td>
</tr>
<tr>
<td><code>wp_bnb_guests_repeat</code></td>
<td><?php esc_html_e( 'Gauge', 'wp-bnb' ); ?></td>
<td><?php esc_html_e( 'Number of repeat guests (more than one booking)', 'wp-bnb' ); ?></td>
</tr>
</tbody>
</table>
<h2 style="margin-top: 30px;"><?php esc_html_e( 'Grafana Dashboard', 'wp-bnb' ); ?></h2>
<?php if ( $dashboard_available ) : ?>
<p>
<?php esc_html_e( 'A pre-configured Grafana dashboard is available for visualizing WP BnB metrics.', 'wp-bnb' ); ?>
</p>
<p>
<strong><?php esc_html_e( 'Dashboard file:', 'wp-bnb' ); ?></strong>
<code>assets/grafana/wp-bnb-dashboard.json</code>
</p>
<p class="description">
<?php esc_html_e( 'If WP Prometheus is installed, this dashboard will be automatically registered and available for export in the Prometheus settings.', 'wp-bnb' ); ?>
</p>
<?php else : ?>
<div class="notice notice-warning inline">
<p><?php esc_html_e( 'Grafana dashboard file not found.', 'wp-bnb' ); ?></p>
</div>
<?php endif; ?>
<p class="submit">
<?php submit_button( __( 'Save Metrics Settings', 'wp-bnb' ), 'primary', 'submit', false ); ?>
</p>
</form>
<?php
}
/**
* Render API settings tab.
*
* @return void
*/
private function render_api_settings(): void {
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Subtab switching only.
$active_subtab = isset( $_GET['subtab'] ) ? sanitize_key( $_GET['subtab'] ) : 'general';
$api_enabled = get_option( 'wp_bnb_api_enabled', 'yes' );
$rate_limiting = get_option( 'wp_bnb_api_rate_limiting', 'yes' );
$api_base_url = rest_url( RestApi::NAMESPACE );
// Rate limit values.
$defaults = \Magdev\WpBnb\Api\RateLimiter::get_default_limits();
$limit_public = get_option( 'wp_bnb_rate_limit_public', $defaults['public'] );
$limit_avail = get_option( 'wp_bnb_rate_limit_availability', $defaults['availability'] );
$limit_booking = get_option( 'wp_bnb_rate_limit_booking', $defaults['booking'] );
$limit_admin = get_option( 'wp_bnb_rate_limit_admin', $defaults['admin'] );
$limit_window = get_option( 'wp_bnb_rate_limit_window', 60 );
$base_url = admin_url( 'admin.php?page=wp-bnb-settings&tab=api' );
?>
<!-- API Subtabs -->
<div class="wp-bnb-subtabs">
<a href="<?php echo esc_url( $base_url . '&subtab=general' ); ?>"
class="wp-bnb-subtab <?php echo 'general' === $active_subtab ? 'active' : ''; ?>">
<span class="dashicons dashicons-admin-generic"></span>
<?php esc_html_e( 'General', 'wp-bnb' ); ?>
</a>
<a href="<?php echo esc_url( $base_url . '&subtab=rate-limits' ); ?>"
class="wp-bnb-subtab <?php echo 'rate-limits' === $active_subtab ? 'active' : ''; ?>">
<span class="dashicons dashicons-dashboard"></span>
<?php esc_html_e( 'Rate Limits', 'wp-bnb' ); ?>
</a>
<a href="<?php echo esc_url( $base_url . '&subtab=endpoints' ); ?>"
class="wp-bnb-subtab <?php echo 'endpoints' === $active_subtab ? 'active' : ''; ?>">
<span class="dashicons dashicons-rest-api"></span>
<?php esc_html_e( 'Endpoints', 'wp-bnb' ); ?>
</a>
</div>
<form method="post" action="">
<?php wp_nonce_field( 'wp_bnb_save_settings', 'wp_bnb_settings_nonce' ); ?>
<?php if ( 'general' === $active_subtab ) : ?>
<!-- General Subtab -->
<h2><?php esc_html_e( 'REST API Settings', 'wp-bnb' ); ?></h2>
<table class="form-table">
<tr>
<th scope="row"><?php esc_html_e( 'Enable API', 'wp-bnb' ); ?></th>
<td>
<label>
<input type="checkbox" name="wp_bnb_api_enabled" value="yes" <?php checked( $api_enabled, 'yes' ); ?>>
<?php esc_html_e( 'Enable the REST API endpoints', 'wp-bnb' ); ?>
</label>
<p class="description">
<?php esc_html_e( 'When enabled, external applications can access room, availability, and booking data via the API.', 'wp-bnb' ); ?>
</p>
</td>
</tr>
<tr>
<th scope="row"><?php esc_html_e( 'Rate Limiting', 'wp-bnb' ); ?></th>
<td>
<label>
<input type="checkbox" name="wp_bnb_api_rate_limiting" value="yes" <?php checked( $rate_limiting, 'yes' ); ?>>
<?php esc_html_e( 'Enable rate limiting', 'wp-bnb' ); ?>
</label>
<p class="description">
<?php esc_html_e( 'Limits API requests to prevent abuse. Recommended for production sites.', 'wp-bnb' ); ?>
<?php if ( 'yes' === $rate_limiting ) : ?>
<a href="<?php echo esc_url( $base_url . '&subtab=rate-limits' ); ?>"><?php esc_html_e( 'Configure limits', 'wp-bnb' ); ?> &rarr;</a>
<?php endif; ?>
</p>
</td>
</tr>
</table>
<h2 style="margin-top: 30px;"><?php esc_html_e( 'API Information', 'wp-bnb' ); ?></h2>
<table class="form-table">
<tr>
<th scope="row"><?php esc_html_e( 'Base URL', 'wp-bnb' ); ?></th>
<td>
<code><?php echo esc_html( $api_base_url ); ?></code>
<p class="description">
<?php esc_html_e( 'All API endpoints are prefixed with this URL.', 'wp-bnb' ); ?>
</p>
</td>
</tr>
<tr>
<th scope="row"><?php esc_html_e( 'API Version', 'wp-bnb' ); ?></th>
<td>
<code><?php echo esc_html( RestApi::VERSION ); ?></code>
</td>
</tr>
<tr>
<th scope="row"><?php esc_html_e( 'Info Endpoint', 'wp-bnb' ); ?></th>
<td>
<code><?php echo esc_html( $api_base_url . '/info' ); ?></code>
<p class="description">
<?php esc_html_e( 'Returns API information and available endpoints.', 'wp-bnb' ); ?>
</p>
</td>
</tr>
</table>
<?php submit_button( __( 'Save Settings', 'wp-bnb' ) ); ?>
<?php elseif ( 'rate-limits' === $active_subtab ) : ?>
<!-- Rate Limits Subtab -->
<h2><?php esc_html_e( 'Rate Limit Configuration', 'wp-bnb' ); ?></h2>
<?php if ( 'yes' !== $rate_limiting ) : ?>
<div class="notice notice-warning inline" style="margin: 15px 0;">
<p>
<?php esc_html_e( 'Rate limiting is currently disabled.', 'wp-bnb' ); ?>
<a href="<?php echo esc_url( $base_url . '&subtab=general' ); ?>"><?php esc_html_e( 'Enable it in General settings', 'wp-bnb' ); ?></a>
</p>
</div>
<?php endif; ?>
<p class="description"><?php esc_html_e( 'Configure the number of requests allowed per time window for each endpoint type.', 'wp-bnb' ); ?></p>
<table class="form-table">
<tr>
<th scope="row"><?php esc_html_e( 'Time Window', 'wp-bnb' ); ?></th>
<td>
<input type="number" name="wp_bnb_rate_limit_window" value="<?php echo esc_attr( $limit_window ); ?>" min="10" max="300" step="10" class="small-text">
<?php esc_html_e( 'seconds', 'wp-bnb' ); ?>
<p class="description">
<?php esc_html_e( 'The time window for rate limit counting. Default: 60 seconds.', 'wp-bnb' ); ?>
</p>
</td>
</tr>
<tr>
<th scope="row"><?php esc_html_e( 'Public Endpoints', 'wp-bnb' ); ?></th>
<td>
<input type="number" name="wp_bnb_rate_limit_public" value="<?php echo esc_attr( $limit_public ); ?>" min="1" max="1000" class="small-text">
<?php esc_html_e( 'requests per window', 'wp-bnb' ); ?>
<p class="description">
<?php
printf(
/* translators: %d: default limit */
esc_html__( 'Limit for public read endpoints (rooms, buildings, services). Default: %d', 'wp-bnb' ),
$defaults['public']
);
?>
</p>
</td>
</tr>
<tr>
<th scope="row"><?php esc_html_e( 'Availability Endpoints', 'wp-bnb' ); ?></th>
<td>
<input type="number" name="wp_bnb_rate_limit_availability" value="<?php echo esc_attr( $limit_avail ); ?>" min="1" max="1000" class="small-text">
<?php esc_html_e( 'requests per window', 'wp-bnb' ); ?>
<p class="description">
<?php
printf(
/* translators: %d: default limit */
esc_html__( 'Limit for availability checks and calendar requests. Default: %d', 'wp-bnb' ),
$defaults['availability']
);
?>
</p>
</td>
</tr>
<tr>
<th scope="row"><?php esc_html_e( 'Booking Endpoints', 'wp-bnb' ); ?></th>
<td>
<input type="number" name="wp_bnb_rate_limit_booking" value="<?php echo esc_attr( $limit_booking ); ?>" min="1" max="100" class="small-text">
<?php esc_html_e( 'requests per window', 'wp-bnb' ); ?>
<p class="description">
<?php
printf(
/* translators: %d: default limit */
esc_html__( 'Limit for booking creation. Keep low to prevent abuse. Default: %d', 'wp-bnb' ),
$defaults['booking']
);
?>
</p>
</td>
</tr>
<tr>
<th scope="row"><?php esc_html_e( 'Admin Endpoints', 'wp-bnb' ); ?></th>
<td>
<input type="number" name="wp_bnb_rate_limit_admin" value="<?php echo esc_attr( $limit_admin ); ?>" min="1" max="1000" class="small-text">
<?php esc_html_e( 'requests per window', 'wp-bnb' ); ?>
<p class="description">
<?php
printf(
/* translators: %d: default limit */
esc_html__( 'Limit for authenticated admin endpoints. Default: %d', 'wp-bnb' ),
$defaults['admin']
);
?>
</p>
</td>
</tr>
</table>
<h2 style="margin-top: 30px;"><?php esc_html_e( 'Current Rate Limits Summary', 'wp-bnb' ); ?></h2>
<table class="widefat striped" style="max-width: 600px;">
<thead>
<tr>
<th><?php esc_html_e( 'Type', 'wp-bnb' ); ?></th>
<th><?php esc_html_e( 'Limit', 'wp-bnb' ); ?></th>
<th><?php esc_html_e( 'Applies To', 'wp-bnb' ); ?></th>
</tr>
</thead>
<tbody>
<tr>
<td><?php esc_html_e( 'Public', 'wp-bnb' ); ?></td>
<td><strong><?php echo esc_html( $limit_public . '/' . $limit_window . 's' ); ?></strong></td>
<td><?php esc_html_e( 'GET rooms, buildings, services', 'wp-bnb' ); ?></td>
</tr>
<tr>
<td><?php esc_html_e( 'Availability', 'wp-bnb' ); ?></td>
<td><strong><?php echo esc_html( $limit_avail . '/' . $limit_window . 's' ); ?></strong></td>
<td><?php esc_html_e( 'Availability checks, calendar', 'wp-bnb' ); ?></td>
</tr>
<tr>
<td><?php esc_html_e( 'Booking', 'wp-bnb' ); ?></td>
<td><strong><?php echo esc_html( $limit_booking . '/' . $limit_window . 's' ); ?></strong></td>
<td><?php esc_html_e( 'Booking creation', 'wp-bnb' ); ?></td>
</tr>
<tr>
<td><?php esc_html_e( 'Admin', 'wp-bnb' ); ?></td>
<td><strong><?php echo esc_html( $limit_admin . '/' . $limit_window . 's' ); ?></strong></td>
<td><?php esc_html_e( 'All admin endpoints', 'wp-bnb' ); ?></td>
</tr>
</tbody>
</table>
<?php submit_button( __( 'Save Rate Limits', 'wp-bnb' ) ); ?>
<?php else : ?>
<!-- Endpoints Subtab -->
<h2><?php esc_html_e( 'Available Endpoints', 'wp-bnb' ); ?></h2>
<h3><?php esc_html_e( 'Public Endpoints', 'wp-bnb' ); ?></h3>
<p class="description"><?php esc_html_e( 'These endpoints are accessible without authentication.', 'wp-bnb' ); ?></p>
<table class="widefat striped" style="margin-bottom: 20px;">
<thead>
<tr>
<th style="width: 80px;"><?php esc_html_e( 'Method', 'wp-bnb' ); ?></th>
<th style="width: 250px;"><?php esc_html_e( 'Endpoint', 'wp-bnb' ); ?></th>
<th><?php esc_html_e( 'Description', 'wp-bnb' ); ?></th>
</tr>
</thead>
<tbody>
<tr><td><span class="wp-bnb-method get">GET</span></td><td><code>/buildings</code></td><td><?php esc_html_e( 'List all buildings', 'wp-bnb' ); ?></td></tr>
<tr><td><span class="wp-bnb-method get">GET</span></td><td><code>/buildings/{id}</code></td><td><?php esc_html_e( 'Get building details', 'wp-bnb' ); ?></td></tr>
<tr><td><span class="wp-bnb-method get">GET</span></td><td><code>/buildings/{id}/rooms</code></td><td><?php esc_html_e( 'List rooms in building', 'wp-bnb' ); ?></td></tr>
<tr><td><span class="wp-bnb-method get">GET</span></td><td><code>/rooms</code></td><td><?php esc_html_e( 'List/search rooms', 'wp-bnb' ); ?></td></tr>
<tr><td><span class="wp-bnb-method get">GET</span></td><td><code>/rooms/{id}</code></td><td><?php esc_html_e( 'Get room details', 'wp-bnb' ); ?></td></tr>
<tr><td><span class="wp-bnb-method get">GET</span></td><td><code>/rooms/{id}/availability</code></td><td><?php esc_html_e( 'Check room availability', 'wp-bnb' ); ?></td></tr>
<tr><td><span class="wp-bnb-method get">GET</span></td><td><code>/rooms/{id}/calendar</code></td><td><?php esc_html_e( 'Get room calendar', 'wp-bnb' ); ?></td></tr>
<tr><td><span class="wp-bnb-method post">POST</span></td><td><code>/availability/search</code></td><td><?php esc_html_e( 'Search available rooms', 'wp-bnb' ); ?></td></tr>
<tr><td><span class="wp-bnb-method get">GET</span></td><td><code>/services</code></td><td><?php esc_html_e( 'List services', 'wp-bnb' ); ?></td></tr>
<tr><td><span class="wp-bnb-method post">POST</span></td><td><code>/pricing/calculate</code></td><td><?php esc_html_e( 'Calculate booking price', 'wp-bnb' ); ?></td></tr>
<tr><td><span class="wp-bnb-method post">POST</span></td><td><code>/bookings</code></td><td><?php esc_html_e( 'Create booking (pending status)', 'wp-bnb' ); ?></td></tr>
</tbody>
</table>
<h3><?php esc_html_e( 'Admin Endpoints', 'wp-bnb' ); ?></h3>
<p class="description"><?php esc_html_e( 'These endpoints require authentication (Application Password or Cookie + Nonce).', 'wp-bnb' ); ?></p>
<table class="widefat striped" style="margin-bottom: 20px;">
<thead>
<tr>
<th style="width: 80px;"><?php esc_html_e( 'Method', 'wp-bnb' ); ?></th>
<th style="width: 250px;"><?php esc_html_e( 'Endpoint', 'wp-bnb' ); ?></th>
<th><?php esc_html_e( 'Description', 'wp-bnb' ); ?></th>
</tr>
</thead>
<tbody>
<tr><td><span class="wp-bnb-method get">GET</span></td><td><code>/bookings</code></td><td><?php esc_html_e( 'List all bookings', 'wp-bnb' ); ?></td></tr>
<tr><td><span class="wp-bnb-method get">GET</span></td><td><code>/bookings/{id}</code></td><td><?php esc_html_e( 'Get booking details', 'wp-bnb' ); ?></td></tr>
<tr><td><span class="wp-bnb-method patch">PATCH</span></td><td><code>/bookings/{id}</code></td><td><?php esc_html_e( 'Update booking', 'wp-bnb' ); ?></td></tr>
<tr><td><span class="wp-bnb-method delete">DELETE</span></td><td><code>/bookings/{id}</code></td><td><?php esc_html_e( 'Cancel booking', 'wp-bnb' ); ?></td></tr>
<tr><td><span class="wp-bnb-method post">POST</span></td><td><code>/bookings/{id}/confirm</code></td><td><?php esc_html_e( 'Confirm booking', 'wp-bnb' ); ?></td></tr>
<tr><td><span class="wp-bnb-method post">POST</span></td><td><code>/bookings/{id}/check-in</code></td><td><?php esc_html_e( 'Check in guest', 'wp-bnb' ); ?></td></tr>
<tr><td><span class="wp-bnb-method post">POST</span></td><td><code>/bookings/{id}/check-out</code></td><td><?php esc_html_e( 'Check out guest', 'wp-bnb' ); ?></td></tr>
<tr><td><span class="wp-bnb-method get">GET</span></td><td><code>/guests</code></td><td><?php esc_html_e( 'List guests', 'wp-bnb' ); ?></td></tr>
<tr><td><span class="wp-bnb-method get">GET</span></td><td><code>/guests/{id}</code></td><td><?php esc_html_e( 'Get guest details', 'wp-bnb' ); ?></td></tr>
<tr><td><span class="wp-bnb-method get">GET</span></td><td><code>/guests/search</code></td><td><?php esc_html_e( 'Search guests', 'wp-bnb' ); ?></td></tr>
</tbody>
</table>
<h2 style="margin-top: 30px;"><?php esc_html_e( 'Authentication', 'wp-bnb' ); ?></h2>
<p><?php esc_html_e( 'Admin endpoints require authentication. Use one of the following methods:', 'wp-bnb' ); ?></p>
<table class="form-table">
<tr>
<th scope="row">
<span class="dashicons dashicons-admin-network" style="color: #2271b1;"></span>
<?php esc_html_e( 'Application Passwords', 'wp-bnb' ); ?>
</th>
<td>
<p><?php esc_html_e( 'Create an Application Password in Users > Your Profile.', 'wp-bnb' ); ?></p>
<p><code>Authorization: Basic base64(username:app-password)</code></p>
<p class="description"><?php esc_html_e( 'Recommended for external applications and integrations.', 'wp-bnb' ); ?></p>
</td>
</tr>
<tr>
<th scope="row">
<span class="dashicons dashicons-lock" style="color: #2271b1;"></span>
<?php esc_html_e( 'Cookie + Nonce', 'wp-bnb' ); ?>
</th>
<td>
<p><?php esc_html_e( 'For same-domain JavaScript requests when user is logged in.', 'wp-bnb' ); ?></p>
<p><code>X-WP-Nonce: <?php echo esc_html( wp_create_nonce( 'wp_rest' ) ); ?></code></p>
<p class="description"><?php esc_html_e( 'Best for frontend JavaScript that interacts with the API.', 'wp-bnb' ); ?></p>
</td>
</tr>
</table>
<?php endif; ?>
</form>
<?php
}
/**
* Render license status badge.
*
* @param string $status License status.
* @return void
*/
private function render_license_status_badge( string $status ): void {
$badges = array(
'valid' => array(
'class' => 'dashicons-yes-alt',
'color' => '#00a32a',
'label' => __( 'Valid', 'wp-bnb' ),
),
'invalid' => array(
'class' => 'dashicons-dismiss',
'color' => '#d63638',
'label' => __( 'Invalid', 'wp-bnb' ),
),
'expired' => array(
'class' => 'dashicons-warning',
'color' => '#dba617',
'label' => __( 'Expired', 'wp-bnb' ),
),
'revoked' => array(
'class' => 'dashicons-dismiss',
'color' => '#d63638',
'label' => __( 'Revoked', 'wp-bnb' ),
),
'inactive' => array(
'class' => 'dashicons-marker',
'color' => '#72aee6',
'label' => __( 'Inactive', 'wp-bnb' ),
),
'unchecked' => array(
'class' => 'dashicons-info',
'color' => '#72aee6',
'label' => __( 'Not checked', 'wp-bnb' ),
),
'unconfigured' => array(
'class' => 'dashicons-admin-generic',
'color' => '#646970',
'label' => __( 'Not configured', 'wp-bnb' ),
),
);
$badge = $badges[ $status ] ?? $badges['unconfigured'];
?>
<span class="wp-bnb-license-status" style="color: <?php echo esc_attr( $badge['color'] ); ?>;">
<span class="dashicons <?php echo esc_attr( $badge['class'] ); ?>"></span>
<?php echo esc_html( $badge['label'] ); ?>
</span>
<?php
}
/**
* Save settings based on active tab.
*
* @param string $tab Active tab.
* @return void
*/
private function save_settings( string $tab ): void {
switch ( $tab ) {
case 'pricing':
$this->save_pricing_settings();
break;
case 'license':
$this->save_license_settings();
break;
case 'updates':
$this->save_updates_settings();
break;
case 'metrics':
$this->save_metrics_settings();
break;
case 'api':
$this->save_api_settings();
break;
case 'woocommerce':
$this->save_woocommerce_settings();
break;
default:
$this->save_general_settings();
break;
}
}
/**
* Save general settings.
*
* @return void
*/
private function save_general_settings(): void {
// Business Information.
if ( isset( $_POST['wp_bnb_business_name'] ) ) {
update_option( 'wp_bnb_business_name', sanitize_text_field( wp_unslash( $_POST['wp_bnb_business_name'] ) ) );
}
if ( isset( $_POST['wp_bnb_currency'] ) ) {
update_option( 'wp_bnb_currency', sanitize_text_field( wp_unslash( $_POST['wp_bnb_currency'] ) ) );
}
// Address fields.
$address_fields = array( 'street', 'city', 'postal', 'country' );
foreach ( $address_fields as $field ) {
$key = 'wp_bnb_address_' . $field;
if ( isset( $_POST[ $key ] ) ) {
update_option( $key, sanitize_text_field( wp_unslash( $_POST[ $key ] ) ) );
}
}
// Contact fields.
if ( isset( $_POST['wp_bnb_contact_email'] ) ) {
update_option( 'wp_bnb_contact_email', sanitize_email( wp_unslash( $_POST['wp_bnb_contact_email'] ) ) );
}
if ( isset( $_POST['wp_bnb_contact_phone'] ) ) {
update_option( 'wp_bnb_contact_phone', sanitize_text_field( wp_unslash( $_POST['wp_bnb_contact_phone'] ) ) );
}
if ( isset( $_POST['wp_bnb_contact_website'] ) ) {
update_option( 'wp_bnb_contact_website', esc_url_raw( wp_unslash( $_POST['wp_bnb_contact_website'] ) ) );
}
// Social media fields.
$social_fields = array( 'facebook', 'instagram', 'x', 'linkedin', 'tripadvisor' );
foreach ( $social_fields as $field ) {
$key = 'wp_bnb_social_' . $field;
if ( isset( $_POST[ $key ] ) ) {
update_option( $key, esc_url_raw( wp_unslash( $_POST[ $key ] ) ) );
}
}
add_settings_error( 'wp_bnb_settings', 'settings_saved', __( 'Settings saved.', 'wp-bnb' ), 'success' );
settings_errors( 'wp_bnb_settings' );
}
/**
* Save pricing settings.
*
* @return void
*/
private function save_pricing_settings(): void {
if ( isset( $_POST['wp_bnb_short_term_max_nights'] ) ) {
$value = absint( $_POST['wp_bnb_short_term_max_nights'] );
$value = max( 1, min( 30, $value ) );
update_option( 'wp_bnb_short_term_max_nights', $value );
}
if ( isset( $_POST['wp_bnb_mid_term_max_nights'] ) ) {
$value = absint( $_POST['wp_bnb_mid_term_max_nights'] );
$value = max( 7, min( 90, $value ) );
update_option( 'wp_bnb_mid_term_max_nights', $value );
}
if ( isset( $_POST['wp_bnb_weekend_days'] ) && is_array( $_POST['wp_bnb_weekend_days'] ) ) {
$days = array_map( 'absint', $_POST['wp_bnb_weekend_days'] );
$days = array_filter( $days, fn( $d ) => $d >= 1 && $d <= 7 );
update_option( 'wp_bnb_weekend_days', implode( ',', $days ) );
} else {
update_option( 'wp_bnb_weekend_days', '' );
}
add_settings_error( 'wp_bnb_settings', 'settings_saved', __( 'Pricing settings saved.', 'wp-bnb' ), 'success' );
settings_errors( 'wp_bnb_settings' );
}
/**
* Save license settings.
*
* @return void
*/
private function save_license_settings(): void {
$data = array(
'license_key' => isset( $_POST['wp_bnb_license_key'] ) ? sanitize_text_field( wp_unslash( $_POST['wp_bnb_license_key'] ) ) : '',
'server_url' => isset( $_POST['wp_bnb_license_server_url'] ) ? esc_url_raw( wp_unslash( $_POST['wp_bnb_license_server_url'] ) ) : '',
'server_secret' => isset( $_POST['wp_bnb_license_server_secret'] ) ? sanitize_text_field( wp_unslash( $_POST['wp_bnb_license_server_secret'] ) ) : '',
);
LicenseManager::save_settings( $data );
add_settings_error( 'wp_bnb_settings', 'settings_saved', __( 'License settings saved.', 'wp-bnb' ), 'success' );
settings_errors( 'wp_bnb_settings' );
}
/**
* Save updates settings.
*
* @return void
*/
private function save_updates_settings(): void {
$notifications_enabled = isset( $_POST['wp_bnb_update_notifications_enabled'] ) ? 'yes' : 'no';
update_option( LicenseUpdater::OPTION_NOTIFICATIONS_ENABLED, $notifications_enabled );
$auto_install_enabled = isset( $_POST['wp_bnb_auto_install_enabled'] ) ? 'yes' : 'no';
update_option( LicenseUpdater::OPTION_AUTO_INSTALL_ENABLED, $auto_install_enabled );
if ( isset( $_POST['wp_bnb_update_check_frequency'] ) ) {
$frequency = absint( $_POST['wp_bnb_update_check_frequency'] );
$frequency = max( 1, min( 168, $frequency ) ); // Clamp between 1-168 hours.
update_option( LicenseUpdater::OPTION_CHECK_FREQUENCY, $frequency );
// Clear update cache when frequency changes so new frequency takes effect.
$updater = LicenseUpdater::get_instance();
if ( null !== $updater ) {
$updater->clear_cache();
}
}
add_settings_error( 'wp_bnb_settings', 'settings_saved', __( 'Update settings saved.', 'wp-bnb' ), 'success' );
settings_errors( 'wp_bnb_settings' );
}
/**
* Save metrics settings.
*
* @return void
*/
private function save_metrics_settings(): void {
$metrics_enabled = isset( $_POST['wp_bnb_metrics_enabled'] ) ? 'yes' : 'no';
update_option( Prometheus::OPTION_ENABLED, $metrics_enabled );
add_settings_error( 'wp_bnb_settings', 'settings_saved', __( 'Metrics settings saved.', 'wp-bnb' ), 'success' );
settings_errors( 'wp_bnb_settings' );
}
/**
* Save API settings.
*
* @return void
*/
private function save_api_settings(): void {
$api_enabled = isset( $_POST['wp_bnb_api_enabled'] ) ? 'yes' : 'no';
$rate_limiting = isset( $_POST['wp_bnb_api_rate_limiting'] ) ? 'yes' : 'no';
update_option( 'wp_bnb_api_enabled', $api_enabled );
update_option( 'wp_bnb_api_rate_limiting', $rate_limiting );
// Save rate limit configuration.
$defaults = \Magdev\WpBnb\Api\RateLimiter::get_default_limits();
$limit_window = isset( $_POST['wp_bnb_rate_limit_window'] )
? max( 10, min( 300, absint( $_POST['wp_bnb_rate_limit_window'] ) ) )
: 60;
$limit_public = isset( $_POST['wp_bnb_rate_limit_public'] )
? max( 1, min( 1000, absint( $_POST['wp_bnb_rate_limit_public'] ) ) )
: $defaults['public'];
$limit_avail = isset( $_POST['wp_bnb_rate_limit_availability'] )
? max( 1, min( 1000, absint( $_POST['wp_bnb_rate_limit_availability'] ) ) )
: $defaults['availability'];
$limit_booking = isset( $_POST['wp_bnb_rate_limit_booking'] )
? max( 1, min( 100, absint( $_POST['wp_bnb_rate_limit_booking'] ) ) )
: $defaults['booking'];
$limit_admin = isset( $_POST['wp_bnb_rate_limit_admin'] )
? max( 1, min( 1000, absint( $_POST['wp_bnb_rate_limit_admin'] ) ) )
: $defaults['admin'];
update_option( 'wp_bnb_rate_limit_window', $limit_window );
update_option( 'wp_bnb_rate_limit_public', $limit_public );
update_option( 'wp_bnb_rate_limit_availability', $limit_avail );
update_option( 'wp_bnb_rate_limit_booking', $limit_booking );
update_option( 'wp_bnb_rate_limit_admin', $limit_admin );
add_settings_error( 'wp_bnb_settings', 'settings_saved', __( 'API settings saved.', 'wp-bnb' ), 'success' );
settings_errors( 'wp_bnb_settings' );
}
/**
* Render WooCommerce settings tab.
*
* @return void
*/
private function render_woocommerce_settings(): void {
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Subtab switching only.
$active_subtab = isset( $_GET['subtab'] ) ? sanitize_key( $_GET['subtab'] ) : 'general';
$base_url = admin_url( 'admin.php?page=wp-bnb-settings&tab=woocommerce' );
$wc_active = class_exists( 'WooCommerce' );
?>
<div class="wp-bnb-subtabs">
<a href="<?php echo esc_url( $base_url . '&subtab=general' ); ?>"
class="wp-bnb-subtab <?php echo 'general' === $active_subtab ? 'active' : ''; ?>">
<span class="dashicons dashicons-admin-generic"></span>
<?php esc_html_e( 'General', 'wp-bnb' ); ?>
</a>
<a href="<?php echo esc_url( $base_url . '&subtab=products' ); ?>"
class="wp-bnb-subtab <?php echo 'products' === $active_subtab ? 'active' : ''; ?>">
<span class="dashicons dashicons-products"></span>
<?php esc_html_e( 'Products', 'wp-bnb' ); ?>
</a>
<a href="<?php echo esc_url( $base_url . '&subtab=orders' ); ?>"
class="wp-bnb-subtab <?php echo 'orders' === $active_subtab ? 'active' : ''; ?>">
<span class="dashicons dashicons-cart"></span>
<?php esc_html_e( 'Orders', 'wp-bnb' ); ?>
</a>
<a href="<?php echo esc_url( $base_url . '&subtab=invoices' ); ?>"
class="wp-bnb-subtab <?php echo 'invoices' === $active_subtab ? 'active' : ''; ?>">
<span class="dashicons dashicons-media-document"></span>
<?php esc_html_e( 'Invoices', 'wp-bnb' ); ?>
</a>
</div>
<?php if ( ! $wc_active ) : ?>
<div class="notice notice-warning inline">
<p>
<strong><?php esc_html_e( 'WooCommerce is not active.', 'wp-bnb' ); ?></strong>
<?php esc_html_e( 'Please install and activate WooCommerce to use these features.', 'wp-bnb' ); ?>
</p>
</div>
<?php endif; ?>
<?php
switch ( $active_subtab ) {
case 'products':
$this->render_wc_products_subtab( $wc_active );
break;
case 'orders':
$this->render_wc_orders_subtab( $wc_active );
break;
case 'invoices':
$this->render_wc_invoices_subtab( $wc_active );
break;
default:
$this->render_wc_general_subtab( $wc_active );
break;
}
}
/**
* Render WooCommerce General subtab.
*
* @param bool $wc_active Whether WooCommerce is active.
* @return void
*/
private function render_wc_general_subtab( bool $wc_active ): void {
?>
<form method="post" action="">
<?php wp_nonce_field( 'wp_bnb_save_settings', 'wp_bnb_settings_nonce' ); ?>
<input type="hidden" name="wc_subtab" value="general">
<h2><?php esc_html_e( 'WooCommerce Integration', 'wp-bnb' ); ?></h2>
<table class="form-table" role="presentation">
<tr>
<th scope="row"><?php esc_html_e( 'Enable Integration', 'wp-bnb' ); ?></th>
<td>
<label for="wp_bnb_wc_enabled">
<input type="checkbox" name="wp_bnb_wc_enabled" id="wp_bnb_wc_enabled" value="yes"
<?php checked( 'yes', get_option( WooCommerceManager::OPTION_ENABLED, 'no' ) ); ?>
<?php disabled( ! $wc_active ); ?>>
<?php esc_html_e( 'Enable WooCommerce integration for bookings', 'wp-bnb' ); ?>
</label>
<p class="description">
<?php esc_html_e( 'Allow guests to book and pay through WooCommerce checkout.', 'wp-bnb' ); ?>
</p>
</td>
</tr>
<tr>
<th scope="row"><?php esc_html_e( 'Auto-Confirm Bookings', 'wp-bnb' ); ?></th>
<td>
<label for="wp_bnb_wc_auto_confirm_booking">
<input type="checkbox" name="wp_bnb_wc_auto_confirm_booking" id="wp_bnb_wc_auto_confirm_booking" value="yes"
<?php checked( 'yes', get_option( WooCommerceManager::OPTION_AUTO_CONFIRM, 'yes' ) ); ?>
<?php disabled( ! $wc_active ); ?>>
<?php esc_html_e( 'Automatically confirm booking when payment is completed', 'wp-bnb' ); ?>
</label>
<p class="description">
<?php esc_html_e( 'If disabled, bookings will remain pending until manually confirmed.', 'wp-bnb' ); ?>
</p>
</td>
</tr>
</table>
<?php if ( $wc_active ) : ?>
<p class="submit">
<input type="submit" name="submit" class="button button-primary"
value="<?php esc_attr_e( 'Save Settings', 'wp-bnb' ); ?>">
</p>
<?php endif; ?>
</form>
<?php
}
/**
* Render WooCommerce Products subtab.
*
* @param bool $wc_active Whether WooCommerce is active.
* @return void
*/
private function render_wc_products_subtab( bool $wc_active ): void {
$categories = array();
if ( $wc_active ) {
$categories = get_terms(
array(
'taxonomy' => 'product_cat',
'hide_empty' => false,
)
);
}
?>
<form method="post" action="">
<?php wp_nonce_field( 'wp_bnb_save_settings', 'wp_bnb_settings_nonce' ); ?>
<input type="hidden" name="wc_subtab" value="products">
<h2><?php esc_html_e( 'Product Synchronization', 'wp-bnb' ); ?></h2>
<table class="form-table" role="presentation">
<tr>
<th scope="row"><?php esc_html_e( 'Auto-Sync Products', 'wp-bnb' ); ?></th>
<td>
<label for="wp_bnb_wc_auto_sync_products">
<input type="checkbox" name="wp_bnb_wc_auto_sync_products" id="wp_bnb_wc_auto_sync_products" value="yes"
<?php checked( 'yes', get_option( WooCommerceManager::OPTION_AUTO_SYNC, 'yes' ) ); ?>
<?php disabled( ! $wc_active ); ?>>
<?php esc_html_e( 'Automatically create/update WooCommerce products when rooms are saved', 'wp-bnb' ); ?>
</label>
</td>
</tr>
<tr>
<th scope="row">
<label for="wp_bnb_wc_product_category"><?php esc_html_e( 'Product Category', 'wp-bnb' ); ?></label>
</th>
<td>
<select name="wp_bnb_wc_product_category" id="wp_bnb_wc_product_category" <?php disabled( ! $wc_active ); ?>>
<option value=""><?php esc_html_e( '— No category —', 'wp-bnb' ); ?></option>
<?php foreach ( $categories as $cat ) : ?>
<option value="<?php echo esc_attr( $cat->term_id ); ?>"
<?php selected( get_option( WooCommerceManager::OPTION_PRODUCT_CATEGORY, 0 ), $cat->term_id ); ?>>
<?php echo esc_html( $cat->name ); ?>
</option>
<?php endforeach; ?>
</select>
<p class="description">
<?php esc_html_e( 'Assign synced room products to this WooCommerce category.', 'wp-bnb' ); ?>
</p>
</td>
</tr>
</table>
<?php if ( $wc_active ) : ?>
<h3><?php esc_html_e( 'Manual Sync', 'wp-bnb' ); ?></h3>
<p>
<button type="button" class="button bnb-sync-rooms-btn">
<span class="dashicons dashicons-update"></span>
<?php esc_html_e( 'Sync All Rooms Now', 'wp-bnb' ); ?>
</button>
<span class="sync-status"></span>
</p>
<p class="description">
<?php esc_html_e( 'Creates or updates WooCommerce products for all published rooms.', 'wp-bnb' ); ?>
</p>
<p class="submit">
<input type="submit" name="submit" class="button button-primary"
value="<?php esc_attr_e( 'Save Settings', 'wp-bnb' ); ?>">
</p>
<?php endif; ?>
</form>
<?php
}
/**
* Render WooCommerce Orders subtab.
*
* @param bool $wc_active Whether WooCommerce is active.
* @return void
*/
private function render_wc_orders_subtab( bool $wc_active ): void {
?>
<h2><?php esc_html_e( 'Order-Booking Status Mapping', 'wp-bnb' ); ?></h2>
<p class="description">
<?php esc_html_e( 'The following table shows how WooCommerce order statuses map to booking statuses.', 'wp-bnb' ); ?>
</p>
<table class="widefat fixed striped" style="max-width: 600px; margin-top: 15px;">
<thead>
<tr>
<th><?php esc_html_e( 'WooCommerce Status', 'wp-bnb' ); ?></th>
<th><?php esc_html_e( 'Booking Status', 'wp-bnb' ); ?></th>
</tr>
</thead>
<tbody>
<tr>
<td><?php esc_html_e( 'Pending Payment', 'wp-bnb' ); ?></td>
<td><span class="bnb-status-badge bnb-status-pending"><?php esc_html_e( 'Pending', 'wp-bnb' ); ?></span></td>
</tr>
<tr>
<td><?php esc_html_e( 'On Hold', 'wp-bnb' ); ?></td>
<td><span class="bnb-status-badge bnb-status-pending"><?php esc_html_e( 'Pending', 'wp-bnb' ); ?></span></td>
</tr>
<tr>
<td><?php esc_html_e( 'Processing', 'wp-bnb' ); ?></td>
<td>
<?php if ( get_option( WooCommerceManager::OPTION_AUTO_CONFIRM, 'yes' ) === 'yes' ) : ?>
<span class="bnb-status-badge bnb-status-confirmed"><?php esc_html_e( 'Confirmed', 'wp-bnb' ); ?></span>
<?php else : ?>
<span class="bnb-status-badge bnb-status-pending"><?php esc_html_e( 'Pending', 'wp-bnb' ); ?></span>
<?php endif; ?>
</td>
</tr>
<tr>
<td><?php esc_html_e( 'Completed', 'wp-bnb' ); ?></td>
<td><span class="bnb-status-badge bnb-status-confirmed"><?php esc_html_e( 'Confirmed', 'wp-bnb' ); ?></span></td>
</tr>
<tr>
<td><?php esc_html_e( 'Cancelled / Refunded / Failed', 'wp-bnb' ); ?></td>
<td><span class="bnb-status-badge bnb-status-cancelled"><?php esc_html_e( 'Cancelled', 'wp-bnb' ); ?></span></td>
</tr>
</tbody>
</table>
<h3><?php esc_html_e( 'Refund Handling', 'wp-bnb' ); ?></h3>
<ul style="list-style: disc; margin-left: 20px;">
<li><?php esc_html_e( 'Full refunds automatically cancel the linked booking.', 'wp-bnb' ); ?></li>
<li><?php esc_html_e( 'Partial refunds are recorded but do not cancel the booking.', 'wp-bnb' ); ?></li>
<li><?php esc_html_e( 'Cancellation emails are sent when a booking is cancelled due to refund.', 'wp-bnb' ); ?></li>
</ul>
<?php
}
/**
* Render WooCommerce Invoices subtab.
*
* @param bool $wc_active Whether WooCommerce is active.
* @return void
*/
private function render_wc_invoices_subtab( bool $wc_active ): void {
?>
<form method="post" action="">
<?php wp_nonce_field( 'wp_bnb_save_settings', 'wp_bnb_settings_nonce' ); ?>
<input type="hidden" name="wc_subtab" value="invoices">
<h2><?php esc_html_e( 'Invoice Settings', 'wp-bnb' ); ?></h2>
<table class="form-table" role="presentation">
<tr>
<th scope="row"><?php esc_html_e( 'Attach to Emails', 'wp-bnb' ); ?></th>
<td>
<label for="wp_bnb_wc_invoice_attach">
<input type="checkbox" name="wp_bnb_wc_invoice_attach" id="wp_bnb_wc_invoice_attach" value="yes"
<?php checked( 'yes', get_option( WooCommerceManager::OPTION_INVOICE_ATTACH, 'yes' ) ); ?>
<?php disabled( ! $wc_active ); ?>>
<?php esc_html_e( 'Attach PDF invoice to order confirmation emails', 'wp-bnb' ); ?>
</label>
</td>
</tr>
<tr>
<th scope="row">
<label for="wp_bnb_invoice_prefix"><?php esc_html_e( 'Invoice Number Prefix', 'wp-bnb' ); ?></label>
</th>
<td>
<input type="text" name="wp_bnb_invoice_prefix" id="wp_bnb_invoice_prefix"
value="<?php echo esc_attr( get_option( WooCommerceManager::OPTION_INVOICE_PREFIX, 'INV-' ) ); ?>"
class="regular-text" <?php disabled( ! $wc_active ); ?>>
<p class="description">
<?php esc_html_e( 'Prefix for invoice numbers (e.g., INV- for INV-00001).', 'wp-bnb' ); ?>
</p>
</td>
</tr>
<tr>
<th scope="row">
<label for="wp_bnb_invoice_start_number"><?php esc_html_e( 'Starting Number', 'wp-bnb' ); ?></label>
</th>
<td>
<input type="number" name="wp_bnb_invoice_start_number" id="wp_bnb_invoice_start_number"
value="<?php echo esc_attr( get_option( WooCommerceManager::OPTION_INVOICE_START_NUMBER, 1000 ) ); ?>"
class="small-text" min="1" <?php disabled( ! $wc_active ); ?>>
<p class="description">
<?php esc_html_e( 'Starting number for new invoices (only applies if no invoices generated yet).', 'wp-bnb' ); ?>
</p>
</td>
</tr>
<tr>
<th scope="row">
<label for="wp_bnb_invoice_footer"><?php esc_html_e( 'Footer Text', 'wp-bnb' ); ?></label>
</th>
<td>
<textarea name="wp_bnb_invoice_footer" id="wp_bnb_invoice_footer" rows="3" class="large-text"
<?php disabled( ! $wc_active ); ?>><?php echo esc_textarea( get_option( WooCommerceManager::OPTION_INVOICE_FOOTER, '' ) ); ?></textarea>
<p class="description">
<?php esc_html_e( 'Custom footer text for invoices (e.g., terms and conditions).', 'wp-bnb' ); ?>
</p>
</td>
</tr>
</table>
<?php if ( $wc_active ) : ?>
<p class="submit">
<input type="submit" name="submit" class="button button-primary"
value="<?php esc_attr_e( 'Save Settings', 'wp-bnb' ); ?>">
</p>
<?php endif; ?>
</form>
<?php
}
/**
* Save WooCommerce settings.
*
* @return void
*/
private function save_woocommerce_settings(): void {
// phpcs:ignore WordPress.Security.NonceVerification.Missing -- Nonce verified in render_settings_page.
$subtab = isset( $_POST['wc_subtab'] ) ? sanitize_key( $_POST['wc_subtab'] ) : 'general';
switch ( $subtab ) {
case 'products':
$auto_sync = isset( $_POST['wp_bnb_wc_auto_sync_products'] ) ? 'yes' : 'no';
$category = isset( $_POST['wp_bnb_wc_product_category'] ) ? absint( $_POST['wp_bnb_wc_product_category'] ) : 0;
update_option( WooCommerceManager::OPTION_AUTO_SYNC, $auto_sync );
update_option( WooCommerceManager::OPTION_PRODUCT_CATEGORY, $category );
break;
case 'invoices':
$attach = isset( $_POST['wp_bnb_wc_invoice_attach'] ) ? 'yes' : 'no';
$prefix = isset( $_POST['wp_bnb_invoice_prefix'] ) ? sanitize_text_field( wp_unslash( $_POST['wp_bnb_invoice_prefix'] ) ) : 'INV-';
$start_number = isset( $_POST['wp_bnb_invoice_start_number'] ) ? absint( $_POST['wp_bnb_invoice_start_number'] ) : 1000;
$footer = isset( $_POST['wp_bnb_invoice_footer'] ) ? sanitize_textarea_field( wp_unslash( $_POST['wp_bnb_invoice_footer'] ) ) : '';
update_option( WooCommerceManager::OPTION_INVOICE_ATTACH, $attach );
update_option( WooCommerceManager::OPTION_INVOICE_PREFIX, $prefix );
update_option( WooCommerceManager::OPTION_INVOICE_START_NUMBER, $start_number );
update_option( WooCommerceManager::OPTION_INVOICE_FOOTER, $footer );
break;
default: // general
$enabled = isset( $_POST['wp_bnb_wc_enabled'] ) ? 'yes' : 'no';
$auto_confirm = isset( $_POST['wp_bnb_wc_auto_confirm_booking'] ) ? 'yes' : 'no';
update_option( WooCommerceManager::OPTION_ENABLED, $enabled );
update_option( WooCommerceManager::OPTION_AUTO_CONFIRM, $auto_confirm );
break;
}
add_settings_error( 'wp_bnb_settings', 'settings_saved', __( 'WooCommerce settings saved.', 'wp-bnb' ), 'success' );
settings_errors( 'wp_bnb_settings' );
}
/**
* AJAX handler for checking room availability.
*
* @return void
*/
public function ajax_check_availability(): void {
check_ajax_referer( 'wp_bnb_admin_nonce', 'nonce' );
if ( ! current_user_can( 'edit_posts' ) ) {
wp_send_json_error(
array( 'message' => __( 'You do not have permission to perform this action.', 'wp-bnb' ) )
);
}
$room_id = isset( $_POST['room_id'] ) ? absint( $_POST['room_id'] ) : 0;
$check_in = isset( $_POST['check_in'] ) ? sanitize_text_field( wp_unslash( $_POST['check_in'] ) ) : '';
$check_out = isset( $_POST['check_out'] ) ? sanitize_text_field( wp_unslash( $_POST['check_out'] ) ) : '';
$exclude = isset( $_POST['exclude_booking'] ) ? absint( $_POST['exclude_booking'] ) : null;
if ( ! $room_id || ! $check_in || ! $check_out ) {
wp_send_json_error(
array( 'message' => __( 'Missing required parameters.', 'wp-bnb' ) )
);
}
// Validate dates.
if ( strtotime( $check_out ) <= strtotime( $check_in ) ) {
wp_send_json_error(
array( 'message' => __( 'Check-out date must be after check-in date.', 'wp-bnb' ) )
);
}
$result = Availability::check_availability_with_price( $room_id, $check_in, $check_out, $exclude );
wp_send_json_success( $result );
}
/**
* AJAX handler for searching guests by email.
*
* @return void
*/
public function ajax_search_guest(): void {
check_ajax_referer( 'wp_bnb_admin_nonce', 'nonce' );
if ( ! current_user_can( 'edit_posts' ) ) {
wp_send_json_error(
array( 'message' => __( 'You do not have permission to perform this action.', 'wp-bnb' ) )
);
}
$search = isset( $_POST['search'] ) ? sanitize_text_field( wp_unslash( $_POST['search'] ) ) : '';
if ( strlen( $search ) < 2 ) {
wp_send_json_success( array( 'guests' => array() ) );
}
// Search by email or name.
$guests = get_posts(
array(
'post_type' => Guest::POST_TYPE,
'post_status' => 'publish',
'posts_per_page' => 10,
'meta_query' => array(
'relation' => 'OR',
array(
'key' => '_bnb_guest_email',
'value' => $search,
'compare' => 'LIKE',
),
array(
'key' => '_bnb_guest_first_name',
'value' => $search,
'compare' => 'LIKE',
),
array(
'key' => '_bnb_guest_last_name',
'value' => $search,
'compare' => 'LIKE',
),
),
)
);
$results = array();
foreach ( $guests as $guest ) {
$status = get_post_meta( $guest->ID, '_bnb_guest_status', true ) ?: 'active';
$results[] = array(
'id' => $guest->ID,
'name' => Guest::get_full_name( $guest->ID ),
'email' => get_post_meta( $guest->ID, '_bnb_guest_email', true ),
'phone' => get_post_meta( $guest->ID, '_bnb_guest_phone', true ),
'status' => $status,
);
}
wp_send_json_success( array( 'guests' => $results ) );
}
/**
* Get Twig environment.
*
* @return Environment
*/
public function get_twig(): Environment {
if ( null === $this->twig ) {
$loader = new FilesystemLoader( WP_BNB_PATH . 'templates' );
$this->twig = new Environment(
$loader,
array(
'cache' => WP_DEBUG ? false : WP_BNB_PATH . 'cache/twig',
'debug' => WP_DEBUG,
)
);
}
return $this->twig;
}
/**
* Render a Twig template.
*
* @param string $template Template name.
* @param array $context Template context.
* @return string
*/
public function render( string $template, array $context = array() ): string {
return $this->get_twig()->render( $template, $context );
}
}