Implement Phase 9: Prometheus Metrics (v0.9.0)
All checks were successful
Create Release Package / build-release (push) Successful in 1m8s
All checks were successful
Create Release Package / build-release (push) Successful in 1m8s
- Add Prometheus metrics integration via wp-prometheus hooks - Create Grafana dashboard JSON with 24 panels - Add metrics settings tab with enable/disable toggle - Expose inventory, booking, occupancy, revenue, and guest metrics Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
30
CHANGELOG.md
30
CHANGELOG.md
@@ -5,6 +5,36 @@ All notable changes to this project will be documented in this file.
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [0.9.0] - 2026-02-03
|
||||
|
||||
### Added
|
||||
|
||||
- Prometheus Metrics Integration:
|
||||
- New `src/Integration/Prometheus.php` class for metrics collection
|
||||
- Integration with wp-prometheus plugin via `wp_prometheus_collect_metrics` hook
|
||||
- Inventory metrics: buildings total, rooms by status, services by status
|
||||
- Booking metrics: bookings by status, check-ins/check-outs today, upcoming 7 days, avg duration
|
||||
- Guest metrics: total guests, guests by status, repeat guests, new guests this month
|
||||
- Occupancy metrics: current rate, monthly rate, occupied rooms, total bed capacity
|
||||
- Revenue metrics: this month, YTD, average booking value, services revenue
|
||||
- Grafana Dashboard:
|
||||
- Pre-configured dashboard at `assets/grafana/wp-bnb-dashboard.json`
|
||||
- Automatic registration with wp-prometheus dashboard provider
|
||||
- Occupancy gauges with color-coded thresholds
|
||||
- Pie charts for bookings, rooms, and guests by status
|
||||
- Revenue and guest statistics panels
|
||||
- Responsive grid layout with 24 panels
|
||||
- Settings page Metrics tab:
|
||||
- Enable/disable metrics collection toggle
|
||||
- WP Prometheus detection with status indicator
|
||||
- Complete metrics reference table
|
||||
- Dashboard file location and export info
|
||||
|
||||
### Changed
|
||||
|
||||
- Plugin.php updated to initialize Prometheus integration
|
||||
- Settings page now has six tabs: General, Pricing, License, Updates, Metrics
|
||||
|
||||
## [0.8.0] - 2026-02-03
|
||||
|
||||
### Added
|
||||
|
||||
57
CLAUDE.md
57
CLAUDE.md
@@ -935,3 +935,60 @@ Admin features always work; frontend requires valid license.
|
||||
- CSV export with BOM (`\xEF\xBB\xBF`) ensures Excel compatibility
|
||||
- Guest data aggregation from bookings uses unique key pattern for anonymous guests
|
||||
- Occupancy calculation: (booked nights / total room nights) * 100
|
||||
|
||||
### 2026-02-03 - Version 0.9.0 (Prometheus Metrics)
|
||||
|
||||
**Completed:**
|
||||
|
||||
- Created `src/Integration/Prometheus.php` class (~700 lines)
|
||||
- Integration with wp-prometheus via `wp_prometheus_collect_metrics` hook
|
||||
- Dashboard registration via `wp_prometheus_register_dashboards` hook
|
||||
- Option to enable/disable metrics collection
|
||||
- Inventory metrics: buildings total, rooms by status, services by status
|
||||
- Booking metrics: by status, check-ins/outs today, upcoming 7 days, avg duration
|
||||
- Guest metrics: total, by status, repeat guests, new this month
|
||||
- Occupancy metrics: current rate, monthly rate, occupied rooms, bed capacity
|
||||
- Revenue metrics: this month, YTD, avg booking value, services revenue
|
||||
- Optimized SQL queries using `$wpdb->prepare()` throughout
|
||||
- Created `assets/grafana/wp-bnb-dashboard.json` Grafana dashboard
|
||||
- 24 panels with responsive grid layout
|
||||
- Occupancy gauges with color-coded thresholds (red < 30%, orange < 50%, yellow < 70%, green ≥ 70%)
|
||||
- Pie charts for bookings, rooms, and guests by status
|
||||
- Revenue stat panels (this month, YTD, avg value, services)
|
||||
- Guest stat panels (total, new, repeat, active services)
|
||||
- Today's activity panels (check-ins, check-outs, upcoming)
|
||||
- Prometheus datasource variable for flexibility
|
||||
- Auto-refresh every 5 minutes
|
||||
- Updated `src/Plugin.php`
|
||||
- Added Prometheus class import
|
||||
- Initialized Prometheus integration in `init_components()`
|
||||
- Added "Metrics" tab to settings page (6 tabs total)
|
||||
- Added `render_metrics_settings()` method with WP Prometheus detection
|
||||
- Added `save_metrics_settings()` method
|
||||
- Metrics reference table showing all available metrics
|
||||
- Updated version to 0.9.0
|
||||
|
||||
**Files Created:**
|
||||
|
||||
- `src/Integration/Prometheus.php` - Prometheus metrics integration class
|
||||
- `assets/grafana/wp-bnb-dashboard.json` - Pre-configured Grafana dashboard
|
||||
|
||||
**Files Changed:**
|
||||
|
||||
- `src/Plugin.php` - Prometheus initialization, metrics settings tab
|
||||
- `wp-bnb.php` - Version bump to 0.9.0 (header and constant)
|
||||
- `CHANGELOG.md` - Added v0.9.0 release notes
|
||||
- `PLAN.md` - Marked Phase 9 as complete
|
||||
- `README.md` - Added Prometheus metrics documentation
|
||||
|
||||
**Learnings:**
|
||||
|
||||
- wp-prometheus uses `wp_prometheus_collect_metrics` action with collector object
|
||||
- Collector provides `register_gauge()` for fluctuating values
|
||||
- Labels are passed as array to `register_gauge()`, values to `set()`
|
||||
- Grafana dashboard JSON requires proper panel IDs and grid positions
|
||||
- Occupancy queries need careful date range handling for month boundaries
|
||||
- Revenue queries use `DECIMAL(10,2)` casting for accurate sums
|
||||
- Metrics should be cached or computed efficiently as they're scraped frequently
|
||||
- Dashboard registration requires file path, title, description, icon, and plugin name
|
||||
- Settings tab detection uses `$prometheus_active` to show WP Prometheus status
|
||||
|
||||
20
PLAN.md
20
PLAN.md
@@ -180,15 +180,23 @@ This document outlines the implementation plan for the WP BnB Management plugin.
|
||||
- [x] Guest statistics
|
||||
- [x] Export functionality (CSV, PDF)
|
||||
|
||||
## Phase 9: Prometheus Metrics (v0.9.0)
|
||||
## Phase 9: Prometheus Metrics (v0.9.0) - Complete
|
||||
|
||||
- [ ] Meanigful Metrics for this Plugin, see <https://src.bundespruefstelle.ch/magdev/wp-prometheus/raw/branch/main/README.md> for implementation details
|
||||
- [ ] Example Grafana-Dashboard, see <https://src.bundespruefstelle.ch/magdev/wp-prometheus/raw/branch/main/README.md> for implementation details
|
||||
- [ ] Update settings page to enable/disable metrics
|
||||
- [x] Meaningful Metrics for this Plugin:
|
||||
- Inventory: buildings, rooms by status, services by status
|
||||
- Bookings: by status, check-ins/check-outs today, upcoming, avg duration
|
||||
- Guests: total, by status, repeat guests, new this month
|
||||
- Occupancy: current rate, monthly rate, occupied rooms, bed capacity
|
||||
- Revenue: this month, YTD, average booking value, services revenue
|
||||
- [x] Example Grafana Dashboard:
|
||||
- Pre-configured dashboard JSON at `assets/grafana/wp-bnb-dashboard.json`
|
||||
- Automatic registration with wp-prometheus
|
||||
- 24 panels with gauges, pie charts, and stat displays
|
||||
- [x] Update settings page to enable/disable metrics
|
||||
|
||||
## Phase 10: Security Audit (v0.10.0)
|
||||
|
||||
- [ ] Check for Wordpress best-practises
|
||||
- [ ] Check for Wordpress best-practices
|
||||
- [ ] Review the code for OWASP Top 10, including XSS, XSRF, SQLi and other critical threads
|
||||
|
||||
## Future Considerations (v1.0.0+)
|
||||
@@ -308,6 +316,6 @@ The plugin will provide extensive hooks for customization:
|
||||
| 0.6.0 | Frontend | Complete |
|
||||
| 0.7.0 | CF7 Integration | Complete |
|
||||
| 0.8.0 | Dashboard | Complete |
|
||||
| 0.9.0 | Prometheus Metrics | TBD |
|
||||
| 0.9.0 | Prometheus Metrics | Complete |
|
||||
| 0.10.0 | Security Audit | TBD |
|
||||
| 1.0.0 | Stable Release | TBD |
|
||||
|
||||
61
README.md
61
README.md
@@ -21,6 +21,7 @@ WP BnB Management enables WordPress to act as a full management system for B&B h
|
||||
- **Contact Form 7 Integration**: Accept booking requests and inquiries through CF7 forms
|
||||
- **Dashboard**: Comprehensive admin dashboard with statistics and charts
|
||||
- **Reports**: Detailed reports with CSV and PDF export
|
||||
- **Prometheus Metrics**: Expose operational metrics for monitoring with Grafana
|
||||
|
||||
### Requirements
|
||||
|
||||
@@ -383,6 +384,66 @@ add_action( 'wp_bnb_before_booking_create', function( $booking_data ) {
|
||||
} );
|
||||
```
|
||||
|
||||
## Prometheus Metrics
|
||||
|
||||
The plugin integrates with [WP Prometheus](https://src.bundespruefstelle.ch/magdev/wp-prometheus) to expose operational metrics for monitoring with Prometheus and Grafana.
|
||||
|
||||
### Enabling Metrics
|
||||
|
||||
1. Install and activate the WP Prometheus plugin
|
||||
2. Navigate to **WP BnB → Settings → Metrics**
|
||||
3. Enable "Expose BnB metrics via Prometheus"
|
||||
4. Metrics will be available at your site's `/metrics/` endpoint
|
||||
|
||||
### Available Metrics
|
||||
|
||||
**Inventory Metrics:**
|
||||
|
||||
- `wp_bnb_buildings_total` - Total number of buildings
|
||||
- `wp_bnb_rooms_total{status}` - Rooms by status (available, occupied, maintenance, inactive)
|
||||
- `wp_bnb_services_total{status}` - Services by status (active, inactive)
|
||||
- `wp_bnb_total_capacity_beds` - Total bed capacity across all rooms
|
||||
|
||||
**Booking Metrics:**
|
||||
|
||||
- `wp_bnb_bookings_total{status}` - Bookings by status (pending, confirmed, checked_in, checked_out, cancelled)
|
||||
- `wp_bnb_checkins_today` - Check-ins scheduled for today
|
||||
- `wp_bnb_checkouts_today` - Check-outs scheduled for today
|
||||
- `wp_bnb_bookings_upcoming_7days` - Bookings starting in next 7 days
|
||||
- `wp_bnb_booking_avg_duration_nights` - Average booking duration
|
||||
|
||||
**Occupancy Metrics:**
|
||||
|
||||
- `wp_bnb_occupancy_rate_current` - Current room occupancy rate (percentage)
|
||||
- `wp_bnb_occupancy_rate_this_month` - Monthly occupancy rate (percentage)
|
||||
- `wp_bnb_rooms_currently_occupied` - Rooms currently occupied
|
||||
|
||||
**Revenue Metrics:**
|
||||
|
||||
- `wp_bnb_revenue_this_month{currency}` - Revenue for current month
|
||||
- `wp_bnb_revenue_ytd{currency}` - Revenue year to date
|
||||
- `wp_bnb_booking_avg_value{currency}` - Average booking value
|
||||
- `wp_bnb_services_revenue_this_month{currency}` - Services revenue this month
|
||||
|
||||
**Guest Metrics:**
|
||||
|
||||
- `wp_bnb_guests_total` - Total registered guests
|
||||
- `wp_bnb_guests_by_status{status}` - Guests by status (active, blocked, vip)
|
||||
- `wp_bnb_guests_repeat` - Guests with more than one booking
|
||||
- `wp_bnb_guests_new_this_month` - New guests this month
|
||||
|
||||
### Grafana Dashboard
|
||||
|
||||
A pre-configured Grafana dashboard is included at `assets/grafana/wp-bnb-dashboard.json`. If WP Prometheus is installed, the dashboard is automatically registered and available for export.
|
||||
|
||||
The dashboard includes:
|
||||
|
||||
- Occupancy gauges with color-coded thresholds
|
||||
- Bookings, rooms, and guests pie charts by status
|
||||
- Revenue and guest statistics panels
|
||||
- Today's check-ins/check-outs
|
||||
- Trend indicators
|
||||
|
||||
## Frequently Asked Questions
|
||||
|
||||
### Do I need a license to use this plugin?
|
||||
|
||||
1580
assets/grafana/wp-bnb-dashboard.json
Normal file
1580
assets/grafana/wp-bnb-dashboard.json
Normal file
File diff suppressed because it is too large
Load Diff
877
src/Integration/Prometheus.php
Normal file
877
src/Integration/Prometheus.php
Normal file
@@ -0,0 +1,877 @@
|
||||
<?php
|
||||
/**
|
||||
* Prometheus Metrics Integration.
|
||||
*
|
||||
* Provides meaningful metrics for monitoring BnB operations.
|
||||
*
|
||||
* @package Magdev\WpBnb\Integration
|
||||
*/
|
||||
|
||||
declare( strict_types=1 );
|
||||
|
||||
namespace Magdev\WpBnb\Integration;
|
||||
|
||||
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;
|
||||
|
||||
/**
|
||||
* Prometheus Metrics Integration class.
|
||||
*
|
||||
* Exposes BnB metrics via the wp-prometheus plugin.
|
||||
*/
|
||||
class Prometheus {
|
||||
|
||||
/**
|
||||
* Option key for enabling metrics.
|
||||
*/
|
||||
public const OPTION_ENABLED = 'wp_bnb_metrics_enabled';
|
||||
|
||||
/**
|
||||
* Initialize the Prometheus integration.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function init(): void {
|
||||
// Only hook if metrics are enabled.
|
||||
if ( ! self::is_enabled() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Hook into wp-prometheus collector.
|
||||
add_action( 'wp_prometheus_collect_metrics', array( self::class, 'collect_metrics' ) );
|
||||
|
||||
// Register Grafana dashboard.
|
||||
add_action( 'wp_prometheus_register_dashboards', array( self::class, 'register_dashboards' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if metrics collection is enabled.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function is_enabled(): bool {
|
||||
return 'yes' === get_option( self::OPTION_ENABLED, 'yes' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable metrics collection.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function enable(): void {
|
||||
update_option( self::OPTION_ENABLED, 'yes' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable metrics collection.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function disable(): void {
|
||||
update_option( self::OPTION_ENABLED, 'no' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Collect and register all BnB metrics.
|
||||
*
|
||||
* @param object $collector The wp-prometheus collector instance.
|
||||
* @return void
|
||||
*/
|
||||
public static function collect_metrics( $collector ): void {
|
||||
self::collect_inventory_metrics( $collector );
|
||||
self::collect_booking_metrics( $collector );
|
||||
self::collect_guest_metrics( $collector );
|
||||
self::collect_occupancy_metrics( $collector );
|
||||
self::collect_revenue_metrics( $collector );
|
||||
}
|
||||
|
||||
/**
|
||||
* Collect inventory metrics (buildings, rooms, services).
|
||||
*
|
||||
* @param object $collector The wp-prometheus collector instance.
|
||||
* @return void
|
||||
*/
|
||||
private static function collect_inventory_metrics( $collector ): void {
|
||||
// Buildings total.
|
||||
$buildings_total = wp_count_posts( Building::POST_TYPE );
|
||||
$gauge = $collector->register_gauge(
|
||||
'wp_bnb_buildings_total',
|
||||
'Total number of buildings',
|
||||
array()
|
||||
);
|
||||
$gauge->set( (int) $buildings_total->publish, array() );
|
||||
|
||||
// Rooms by status.
|
||||
$rooms_gauge = $collector->register_gauge(
|
||||
'wp_bnb_rooms_total',
|
||||
'Total number of rooms by status',
|
||||
array( 'status' )
|
||||
);
|
||||
|
||||
$room_statuses = array( 'available', 'occupied', 'maintenance', 'inactive' );
|
||||
foreach ( $room_statuses as $status ) {
|
||||
$count = self::count_rooms_by_status( $status );
|
||||
$rooms_gauge->set( $count, array( $status ) );
|
||||
}
|
||||
|
||||
// Services by status.
|
||||
$services_gauge = $collector->register_gauge(
|
||||
'wp_bnb_services_total',
|
||||
'Total number of services by status',
|
||||
array( 'status' )
|
||||
);
|
||||
|
||||
$service_statuses = array( 'active', 'inactive' );
|
||||
foreach ( $service_statuses as $status ) {
|
||||
$count = self::count_services_by_status( $status );
|
||||
$services_gauge->set( $count, array( $status ) );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Collect booking metrics.
|
||||
*
|
||||
* @param object $collector The wp-prometheus collector instance.
|
||||
* @return void
|
||||
*/
|
||||
private static function collect_booking_metrics( $collector ): void {
|
||||
// Bookings by status.
|
||||
$bookings_gauge = $collector->register_gauge(
|
||||
'wp_bnb_bookings_total',
|
||||
'Total number of bookings by status',
|
||||
array( 'status' )
|
||||
);
|
||||
|
||||
$booking_statuses = array( 'pending', 'confirmed', 'checked_in', 'checked_out', 'cancelled' );
|
||||
foreach ( $booking_statuses as $status ) {
|
||||
$count = self::count_bookings_by_status( $status );
|
||||
$bookings_gauge->set( $count, array( $status ) );
|
||||
}
|
||||
|
||||
// Today's check-ins.
|
||||
$checkins_gauge = $collector->register_gauge(
|
||||
'wp_bnb_checkins_today',
|
||||
'Number of check-ins scheduled for today',
|
||||
array()
|
||||
);
|
||||
$checkins_gauge->set( self::count_todays_checkins(), array() );
|
||||
|
||||
// Today's check-outs.
|
||||
$checkouts_gauge = $collector->register_gauge(
|
||||
'wp_bnb_checkouts_today',
|
||||
'Number of check-outs scheduled for today',
|
||||
array()
|
||||
);
|
||||
$checkouts_gauge->set( self::count_todays_checkouts(), array() );
|
||||
|
||||
// Upcoming bookings (next 7 days).
|
||||
$upcoming_gauge = $collector->register_gauge(
|
||||
'wp_bnb_bookings_upcoming_7days',
|
||||
'Number of bookings starting in the next 7 days',
|
||||
array()
|
||||
);
|
||||
$upcoming_gauge->set( self::count_upcoming_bookings( 7 ), array() );
|
||||
|
||||
// Average booking duration (nights).
|
||||
$avg_duration = $collector->register_gauge(
|
||||
'wp_bnb_booking_avg_duration_nights',
|
||||
'Average booking duration in nights',
|
||||
array()
|
||||
);
|
||||
$avg_duration->set( self::get_average_booking_duration(), array() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Collect guest metrics.
|
||||
*
|
||||
* @param object $collector The wp-prometheus collector instance.
|
||||
* @return void
|
||||
*/
|
||||
private static function collect_guest_metrics( $collector ): void {
|
||||
// Total guests.
|
||||
$guests_total = wp_count_posts( Guest::POST_TYPE );
|
||||
$guests_gauge = $collector->register_gauge(
|
||||
'wp_bnb_guests_total',
|
||||
'Total number of registered guests',
|
||||
array()
|
||||
);
|
||||
$guests_gauge->set( (int) $guests_total->publish, array() );
|
||||
|
||||
// Guests by status.
|
||||
$guests_status_gauge = $collector->register_gauge(
|
||||
'wp_bnb_guests_by_status',
|
||||
'Number of guests by status',
|
||||
array( 'status' )
|
||||
);
|
||||
|
||||
$guest_statuses = array( 'active', 'blocked', 'vip' );
|
||||
foreach ( $guest_statuses as $status ) {
|
||||
$count = self::count_guests_by_status( $status );
|
||||
$guests_status_gauge->set( $count, array( $status ) );
|
||||
}
|
||||
|
||||
// Repeat guests (guests with more than one booking).
|
||||
$repeat_gauge = $collector->register_gauge(
|
||||
'wp_bnb_guests_repeat',
|
||||
'Number of guests with more than one booking',
|
||||
array()
|
||||
);
|
||||
$repeat_gauge->set( self::count_repeat_guests(), array() );
|
||||
|
||||
// New guests this month.
|
||||
$new_guests_gauge = $collector->register_gauge(
|
||||
'wp_bnb_guests_new_this_month',
|
||||
'Number of new guests registered this month',
|
||||
array()
|
||||
);
|
||||
$new_guests_gauge->set( self::count_new_guests_this_month(), array() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Collect occupancy metrics.
|
||||
*
|
||||
* @param object $collector The wp-prometheus collector instance.
|
||||
* @return void
|
||||
*/
|
||||
private static function collect_occupancy_metrics( $collector ): void {
|
||||
// Current occupancy rate (percentage).
|
||||
$occupancy_gauge = $collector->register_gauge(
|
||||
'wp_bnb_occupancy_rate_current',
|
||||
'Current room occupancy rate (percentage)',
|
||||
array()
|
||||
);
|
||||
$occupancy_gauge->set( self::get_current_occupancy_rate(), array() );
|
||||
|
||||
// Occupancy rate this month.
|
||||
$occupancy_month_gauge = $collector->register_gauge(
|
||||
'wp_bnb_occupancy_rate_this_month',
|
||||
'Room occupancy rate for the current month (percentage)',
|
||||
array()
|
||||
);
|
||||
$occupancy_month_gauge->set( self::get_monthly_occupancy_rate(), array() );
|
||||
|
||||
// Rooms currently occupied.
|
||||
$occupied_gauge = $collector->register_gauge(
|
||||
'wp_bnb_rooms_currently_occupied',
|
||||
'Number of rooms currently occupied',
|
||||
array()
|
||||
);
|
||||
$occupied_gauge->set( self::count_currently_occupied_rooms(), array() );
|
||||
|
||||
// Total room capacity (beds).
|
||||
$capacity_gauge = $collector->register_gauge(
|
||||
'wp_bnb_total_capacity_beds',
|
||||
'Total bed capacity across all rooms',
|
||||
array()
|
||||
);
|
||||
$capacity_gauge->set( self::get_total_bed_capacity(), array() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Collect revenue metrics.
|
||||
*
|
||||
* @param object $collector The wp-prometheus collector instance.
|
||||
* @return void
|
||||
*/
|
||||
private static function collect_revenue_metrics( $collector ): void {
|
||||
$currency = get_option( 'wp_bnb_currency', 'CHF' );
|
||||
|
||||
// Revenue this month.
|
||||
$revenue_month_gauge = $collector->register_gauge(
|
||||
'wp_bnb_revenue_this_month',
|
||||
'Total revenue for the current month',
|
||||
array( 'currency' )
|
||||
);
|
||||
$revenue_month_gauge->set( self::get_revenue_this_month(), array( $currency ) );
|
||||
|
||||
// Revenue year to date.
|
||||
$revenue_ytd_gauge = $collector->register_gauge(
|
||||
'wp_bnb_revenue_ytd',
|
||||
'Total revenue year to date',
|
||||
array( 'currency' )
|
||||
);
|
||||
$revenue_ytd_gauge->set( self::get_revenue_ytd(), array( $currency ) );
|
||||
|
||||
// Average booking value.
|
||||
$avg_value_gauge = $collector->register_gauge(
|
||||
'wp_bnb_booking_avg_value',
|
||||
'Average booking value',
|
||||
array( 'currency' )
|
||||
);
|
||||
$avg_value_gauge->set( self::get_average_booking_value(), array( $currency ) );
|
||||
|
||||
// Revenue from services this month.
|
||||
$services_revenue_gauge = $collector->register_gauge(
|
||||
'wp_bnb_services_revenue_this_month',
|
||||
'Revenue from additional services this month',
|
||||
array( 'currency' )
|
||||
);
|
||||
$services_revenue_gauge->set( self::get_services_revenue_this_month(), array( $currency ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Register Grafana dashboards.
|
||||
*
|
||||
* @param object $provider The wp-prometheus dashboard provider instance.
|
||||
* @return void
|
||||
*/
|
||||
public static function register_dashboards( $provider ): void {
|
||||
$dashboard_file = WP_BNB_PATH . 'assets/grafana/wp-bnb-dashboard.json';
|
||||
|
||||
if ( file_exists( $dashboard_file ) ) {
|
||||
$provider->register_dashboard(
|
||||
'wp-bnb',
|
||||
array(
|
||||
'title' => __( 'WP BnB Dashboard', 'wp-bnb' ),
|
||||
'description' => __( 'Monitor occupancy, bookings, revenue, and guest statistics for your B&B.', 'wp-bnb' ),
|
||||
'icon' => 'dashicons-building',
|
||||
'file' => $dashboard_file,
|
||||
'plugin' => 'WP BnB Manager',
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// Helper Methods - Inventory
|
||||
// =========================================================================
|
||||
|
||||
/**
|
||||
* Count rooms by status.
|
||||
*
|
||||
* @param string $status Room status.
|
||||
* @return int
|
||||
*/
|
||||
private static function count_rooms_by_status( string $status ): int {
|
||||
global $wpdb;
|
||||
|
||||
return (int) $wpdb->get_var(
|
||||
$wpdb->prepare(
|
||||
"SELECT COUNT(DISTINCT p.ID)
|
||||
FROM {$wpdb->posts} p
|
||||
INNER JOIN {$wpdb->postmeta} pm ON p.ID = pm.post_id
|
||||
WHERE p.post_type = %s
|
||||
AND p.post_status = 'publish'
|
||||
AND pm.meta_key = '_bnb_room_status'
|
||||
AND pm.meta_value = %s",
|
||||
Room::POST_TYPE,
|
||||
$status
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Count services by status.
|
||||
*
|
||||
* @param string $status Service status.
|
||||
* @return int
|
||||
*/
|
||||
private static function count_services_by_status( string $status ): int {
|
||||
global $wpdb;
|
||||
|
||||
return (int) $wpdb->get_var(
|
||||
$wpdb->prepare(
|
||||
"SELECT COUNT(DISTINCT p.ID)
|
||||
FROM {$wpdb->posts} p
|
||||
INNER JOIN {$wpdb->postmeta} pm ON p.ID = pm.post_id
|
||||
WHERE p.post_type = %s
|
||||
AND p.post_status = 'publish'
|
||||
AND pm.meta_key = '_bnb_service_status'
|
||||
AND pm.meta_value = %s",
|
||||
Service::POST_TYPE,
|
||||
$status
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// Helper Methods - Bookings
|
||||
// =========================================================================
|
||||
|
||||
/**
|
||||
* Count bookings by status.
|
||||
*
|
||||
* @param string $status Booking status.
|
||||
* @return int
|
||||
*/
|
||||
private static function count_bookings_by_status( string $status ): int {
|
||||
global $wpdb;
|
||||
|
||||
return (int) $wpdb->get_var(
|
||||
$wpdb->prepare(
|
||||
"SELECT COUNT(DISTINCT p.ID)
|
||||
FROM {$wpdb->posts} p
|
||||
INNER JOIN {$wpdb->postmeta} pm ON p.ID = pm.post_id
|
||||
WHERE p.post_type = %s
|
||||
AND p.post_status = 'publish'
|
||||
AND pm.meta_key = '_bnb_booking_status'
|
||||
AND pm.meta_value = %s",
|
||||
Booking::POST_TYPE,
|
||||
$status
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Count today's check-ins.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
private static function count_todays_checkins(): int {
|
||||
global $wpdb;
|
||||
$today = gmdate( 'Y-m-d' );
|
||||
|
||||
return (int) $wpdb->get_var(
|
||||
$wpdb->prepare(
|
||||
"SELECT COUNT(DISTINCT p.ID)
|
||||
FROM {$wpdb->posts} p
|
||||
INNER JOIN {$wpdb->postmeta} pm_date ON p.ID = pm_date.post_id
|
||||
INNER JOIN {$wpdb->postmeta} pm_status ON p.ID = pm_status.post_id
|
||||
WHERE p.post_type = %s
|
||||
AND p.post_status = 'publish'
|
||||
AND pm_date.meta_key = '_bnb_booking_check_in'
|
||||
AND pm_date.meta_value = %s
|
||||
AND pm_status.meta_key = '_bnb_booking_status'
|
||||
AND pm_status.meta_value IN ('confirmed', 'pending')",
|
||||
Booking::POST_TYPE,
|
||||
$today
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Count today's check-outs.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
private static function count_todays_checkouts(): int {
|
||||
global $wpdb;
|
||||
$today = gmdate( 'Y-m-d' );
|
||||
|
||||
return (int) $wpdb->get_var(
|
||||
$wpdb->prepare(
|
||||
"SELECT COUNT(DISTINCT p.ID)
|
||||
FROM {$wpdb->posts} p
|
||||
INNER JOIN {$wpdb->postmeta} pm_date ON p.ID = pm_date.post_id
|
||||
INNER JOIN {$wpdb->postmeta} pm_status ON p.ID = pm_status.post_id
|
||||
WHERE p.post_type = %s
|
||||
AND p.post_status = 'publish'
|
||||
AND pm_date.meta_key = '_bnb_booking_check_out'
|
||||
AND pm_date.meta_value = %s
|
||||
AND pm_status.meta_key = '_bnb_booking_status'
|
||||
AND pm_status.meta_value = 'checked_in'",
|
||||
Booking::POST_TYPE,
|
||||
$today
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Count upcoming bookings within given days.
|
||||
*
|
||||
* @param int $days Number of days to look ahead.
|
||||
* @return int
|
||||
*/
|
||||
private static function count_upcoming_bookings( int $days ): int {
|
||||
global $wpdb;
|
||||
$today = gmdate( 'Y-m-d' );
|
||||
$end_date = gmdate( 'Y-m-d', strtotime( "+{$days} days" ) );
|
||||
|
||||
return (int) $wpdb->get_var(
|
||||
$wpdb->prepare(
|
||||
"SELECT COUNT(DISTINCT p.ID)
|
||||
FROM {$wpdb->posts} p
|
||||
INNER JOIN {$wpdb->postmeta} pm_date ON p.ID = pm_date.post_id
|
||||
INNER JOIN {$wpdb->postmeta} pm_status ON p.ID = pm_status.post_id
|
||||
WHERE p.post_type = %s
|
||||
AND p.post_status = 'publish'
|
||||
AND pm_date.meta_key = '_bnb_booking_check_in'
|
||||
AND pm_date.meta_value >= %s
|
||||
AND pm_date.meta_value <= %s
|
||||
AND pm_status.meta_key = '_bnb_booking_status'
|
||||
AND pm_status.meta_value IN ('confirmed', 'pending')",
|
||||
Booking::POST_TYPE,
|
||||
$today,
|
||||
$end_date
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get average booking duration in nights.
|
||||
*
|
||||
* @return float
|
||||
*/
|
||||
private static function get_average_booking_duration(): float {
|
||||
global $wpdb;
|
||||
|
||||
$result = $wpdb->get_var(
|
||||
$wpdb->prepare(
|
||||
"SELECT AVG(DATEDIFF(pm_out.meta_value, pm_in.meta_value))
|
||||
FROM {$wpdb->posts} p
|
||||
INNER JOIN {$wpdb->postmeta} pm_in ON p.ID = pm_in.post_id
|
||||
INNER JOIN {$wpdb->postmeta} pm_out ON p.ID = pm_out.post_id
|
||||
INNER JOIN {$wpdb->postmeta} pm_status ON p.ID = pm_status.post_id
|
||||
WHERE p.post_type = %s
|
||||
AND p.post_status = 'publish'
|
||||
AND pm_in.meta_key = '_bnb_booking_check_in'
|
||||
AND pm_out.meta_key = '_bnb_booking_check_out'
|
||||
AND pm_status.meta_key = '_bnb_booking_status'
|
||||
AND pm_status.meta_value NOT IN ('cancelled')",
|
||||
Booking::POST_TYPE
|
||||
)
|
||||
);
|
||||
|
||||
return round( (float) $result, 1 );
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// Helper Methods - Guests
|
||||
// =========================================================================
|
||||
|
||||
/**
|
||||
* Count guests by status.
|
||||
*
|
||||
* @param string $status Guest status.
|
||||
* @return int
|
||||
*/
|
||||
private static function count_guests_by_status( string $status ): int {
|
||||
global $wpdb;
|
||||
|
||||
return (int) $wpdb->get_var(
|
||||
$wpdb->prepare(
|
||||
"SELECT COUNT(DISTINCT p.ID)
|
||||
FROM {$wpdb->posts} p
|
||||
INNER JOIN {$wpdb->postmeta} pm ON p.ID = pm.post_id
|
||||
WHERE p.post_type = %s
|
||||
AND p.post_status = 'publish'
|
||||
AND pm.meta_key = '_bnb_guest_status'
|
||||
AND pm.meta_value = %s",
|
||||
Guest::POST_TYPE,
|
||||
$status
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Count repeat guests (more than one booking).
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
private static function count_repeat_guests(): int {
|
||||
global $wpdb;
|
||||
|
||||
return (int) $wpdb->get_var(
|
||||
$wpdb->prepare(
|
||||
"SELECT COUNT(DISTINCT pm.meta_value)
|
||||
FROM {$wpdb->postmeta} pm
|
||||
INNER JOIN {$wpdb->posts} p ON pm.post_id = p.ID
|
||||
WHERE p.post_type = %s
|
||||
AND p.post_status = 'publish'
|
||||
AND pm.meta_key = '_bnb_booking_guest_id'
|
||||
AND pm.meta_value != ''
|
||||
GROUP BY pm.meta_value
|
||||
HAVING COUNT(*) > 1",
|
||||
Booking::POST_TYPE
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Count new guests registered this month.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
private static function count_new_guests_this_month(): int {
|
||||
global $wpdb;
|
||||
$first_of_month = gmdate( 'Y-m-01 00:00:00' );
|
||||
|
||||
return (int) $wpdb->get_var(
|
||||
$wpdb->prepare(
|
||||
"SELECT COUNT(*)
|
||||
FROM {$wpdb->posts}
|
||||
WHERE post_type = %s
|
||||
AND post_status = 'publish'
|
||||
AND post_date >= %s",
|
||||
Guest::POST_TYPE,
|
||||
$first_of_month
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// Helper Methods - Occupancy
|
||||
// =========================================================================
|
||||
|
||||
/**
|
||||
* Get current occupancy rate (percentage of rooms occupied today).
|
||||
*
|
||||
* @return float
|
||||
*/
|
||||
private static function get_current_occupancy_rate(): float {
|
||||
$total_rooms = self::count_available_rooms();
|
||||
if ( $total_rooms <= 0 ) {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
$occupied = self::count_currently_occupied_rooms();
|
||||
return round( ( $occupied / $total_rooms ) * 100, 1 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get monthly occupancy rate.
|
||||
*
|
||||
* @return float
|
||||
*/
|
||||
private static function get_monthly_occupancy_rate(): float {
|
||||
global $wpdb;
|
||||
|
||||
$total_rooms = self::count_available_rooms();
|
||||
if ( $total_rooms <= 0 ) {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
$first_of_month = gmdate( 'Y-m-01' );
|
||||
$today = gmdate( 'Y-m-d' );
|
||||
$days_so_far = (int) gmdate( 'd' );
|
||||
$total_room_nights = $total_rooms * $days_so_far;
|
||||
|
||||
// Count booked nights this month (simplified: count bookings that overlap with this month).
|
||||
$booked_nights = (int) $wpdb->get_var(
|
||||
$wpdb->prepare(
|
||||
"SELECT SUM(
|
||||
DATEDIFF(
|
||||
LEAST(pm_out.meta_value, %s),
|
||||
GREATEST(pm_in.meta_value, %s)
|
||||
)
|
||||
)
|
||||
FROM {$wpdb->posts} p
|
||||
INNER JOIN {$wpdb->postmeta} pm_in ON p.ID = pm_in.post_id
|
||||
INNER JOIN {$wpdb->postmeta} pm_out ON p.ID = pm_out.post_id
|
||||
INNER JOIN {$wpdb->postmeta} pm_status ON p.ID = pm_status.post_id
|
||||
WHERE p.post_type = %s
|
||||
AND p.post_status = 'publish'
|
||||
AND pm_in.meta_key = '_bnb_booking_check_in'
|
||||
AND pm_out.meta_key = '_bnb_booking_check_out'
|
||||
AND pm_status.meta_key = '_bnb_booking_status'
|
||||
AND pm_status.meta_value IN ('confirmed', 'checked_in', 'checked_out')
|
||||
AND pm_in.meta_value <= %s
|
||||
AND pm_out.meta_value >= %s",
|
||||
$today,
|
||||
$first_of_month,
|
||||
Booking::POST_TYPE,
|
||||
$today,
|
||||
$first_of_month
|
||||
)
|
||||
);
|
||||
|
||||
if ( $total_room_nights <= 0 ) {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
return round( ( $booked_nights / $total_room_nights ) * 100, 1 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Count currently occupied rooms.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
private static function count_currently_occupied_rooms(): int {
|
||||
global $wpdb;
|
||||
$today = gmdate( 'Y-m-d' );
|
||||
|
||||
return (int) $wpdb->get_var(
|
||||
$wpdb->prepare(
|
||||
"SELECT COUNT(DISTINCT pm_room.meta_value)
|
||||
FROM {$wpdb->posts} p
|
||||
INNER JOIN {$wpdb->postmeta} pm_room ON p.ID = pm_room.post_id
|
||||
INNER JOIN {$wpdb->postmeta} pm_in ON p.ID = pm_in.post_id
|
||||
INNER JOIN {$wpdb->postmeta} pm_out ON p.ID = pm_out.post_id
|
||||
INNER JOIN {$wpdb->postmeta} pm_status ON p.ID = pm_status.post_id
|
||||
WHERE p.post_type = %s
|
||||
AND p.post_status = 'publish'
|
||||
AND pm_room.meta_key = '_bnb_booking_room_id'
|
||||
AND pm_in.meta_key = '_bnb_booking_check_in'
|
||||
AND pm_out.meta_key = '_bnb_booking_check_out'
|
||||
AND pm_status.meta_key = '_bnb_booking_status'
|
||||
AND pm_status.meta_value IN ('confirmed', 'checked_in')
|
||||
AND pm_in.meta_value <= %s
|
||||
AND pm_out.meta_value > %s",
|
||||
Booking::POST_TYPE,
|
||||
$today,
|
||||
$today
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Count available rooms (not in maintenance/inactive).
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
private static function count_available_rooms(): int {
|
||||
global $wpdb;
|
||||
|
||||
return (int) $wpdb->get_var(
|
||||
$wpdb->prepare(
|
||||
"SELECT COUNT(DISTINCT p.ID)
|
||||
FROM {$wpdb->posts} p
|
||||
LEFT JOIN {$wpdb->postmeta} pm ON p.ID = pm.post_id AND pm.meta_key = '_bnb_room_status'
|
||||
WHERE p.post_type = %s
|
||||
AND p.post_status = 'publish'
|
||||
AND (pm.meta_value IS NULL OR pm.meta_value IN ('available', 'occupied'))",
|
||||
Room::POST_TYPE
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get total bed capacity.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
private static function get_total_bed_capacity(): int {
|
||||
global $wpdb;
|
||||
|
||||
$result = $wpdb->get_var(
|
||||
$wpdb->prepare(
|
||||
"SELECT SUM(CAST(pm.meta_value AS UNSIGNED))
|
||||
FROM {$wpdb->posts} p
|
||||
INNER JOIN {$wpdb->postmeta} pm ON p.ID = pm.post_id
|
||||
WHERE p.post_type = %s
|
||||
AND p.post_status = 'publish'
|
||||
AND pm.meta_key = '_bnb_room_beds'",
|
||||
Room::POST_TYPE
|
||||
)
|
||||
);
|
||||
|
||||
return (int) $result;
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// Helper Methods - Revenue
|
||||
// =========================================================================
|
||||
|
||||
/**
|
||||
* Get revenue for the current month.
|
||||
*
|
||||
* @return float
|
||||
*/
|
||||
private static function get_revenue_this_month(): float {
|
||||
global $wpdb;
|
||||
$first_of_month = gmdate( 'Y-m-01' );
|
||||
|
||||
$result = $wpdb->get_var(
|
||||
$wpdb->prepare(
|
||||
"SELECT SUM(CAST(pm_price.meta_value AS DECIMAL(10,2)))
|
||||
FROM {$wpdb->posts} p
|
||||
INNER JOIN {$wpdb->postmeta} pm_price ON p.ID = pm_price.post_id
|
||||
INNER JOIN {$wpdb->postmeta} pm_in ON p.ID = pm_in.post_id
|
||||
INNER JOIN {$wpdb->postmeta} pm_status ON p.ID = pm_status.post_id
|
||||
WHERE p.post_type = %s
|
||||
AND p.post_status = 'publish'
|
||||
AND pm_price.meta_key = '_bnb_booking_total_price'
|
||||
AND pm_in.meta_key = '_bnb_booking_check_in'
|
||||
AND pm_in.meta_value >= %s
|
||||
AND pm_status.meta_key = '_bnb_booking_status'
|
||||
AND pm_status.meta_value IN ('confirmed', 'checked_in', 'checked_out')",
|
||||
Booking::POST_TYPE,
|
||||
$first_of_month
|
||||
)
|
||||
);
|
||||
|
||||
return round( (float) $result, 2 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get revenue year to date.
|
||||
*
|
||||
* @return float
|
||||
*/
|
||||
private static function get_revenue_ytd(): float {
|
||||
global $wpdb;
|
||||
$first_of_year = gmdate( 'Y-01-01' );
|
||||
|
||||
$result = $wpdb->get_var(
|
||||
$wpdb->prepare(
|
||||
"SELECT SUM(CAST(pm_price.meta_value AS DECIMAL(10,2)))
|
||||
FROM {$wpdb->posts} p
|
||||
INNER JOIN {$wpdb->postmeta} pm_price ON p.ID = pm_price.post_id
|
||||
INNER JOIN {$wpdb->postmeta} pm_in ON p.ID = pm_in.post_id
|
||||
INNER JOIN {$wpdb->postmeta} pm_status ON p.ID = pm_status.post_id
|
||||
WHERE p.post_type = %s
|
||||
AND p.post_status = 'publish'
|
||||
AND pm_price.meta_key = '_bnb_booking_total_price'
|
||||
AND pm_in.meta_key = '_bnb_booking_check_in'
|
||||
AND pm_in.meta_value >= %s
|
||||
AND pm_status.meta_key = '_bnb_booking_status'
|
||||
AND pm_status.meta_value IN ('confirmed', 'checked_in', 'checked_out')",
|
||||
Booking::POST_TYPE,
|
||||
$first_of_year
|
||||
)
|
||||
);
|
||||
|
||||
return round( (float) $result, 2 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get average booking value.
|
||||
*
|
||||
* @return float
|
||||
*/
|
||||
private static function get_average_booking_value(): float {
|
||||
global $wpdb;
|
||||
|
||||
$result = $wpdb->get_var(
|
||||
$wpdb->prepare(
|
||||
"SELECT AVG(CAST(pm_price.meta_value AS DECIMAL(10,2)))
|
||||
FROM {$wpdb->posts} p
|
||||
INNER JOIN {$wpdb->postmeta} pm_price ON p.ID = pm_price.post_id
|
||||
INNER JOIN {$wpdb->postmeta} pm_status ON p.ID = pm_status.post_id
|
||||
WHERE p.post_type = %s
|
||||
AND p.post_status = 'publish'
|
||||
AND pm_price.meta_key = '_bnb_booking_total_price'
|
||||
AND pm_status.meta_key = '_bnb_booking_status'
|
||||
AND pm_status.meta_value IN ('confirmed', 'checked_in', 'checked_out')",
|
||||
Booking::POST_TYPE
|
||||
)
|
||||
);
|
||||
|
||||
return round( (float) $result, 2 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get revenue from services this month.
|
||||
*
|
||||
* @return float
|
||||
*/
|
||||
private static function get_services_revenue_this_month(): float {
|
||||
global $wpdb;
|
||||
$first_of_month = gmdate( 'Y-m-01' );
|
||||
|
||||
$result = $wpdb->get_var(
|
||||
$wpdb->prepare(
|
||||
"SELECT SUM(CAST(pm_services.meta_value AS DECIMAL(10,2)))
|
||||
FROM {$wpdb->posts} p
|
||||
INNER JOIN {$wpdb->postmeta} pm_services ON p.ID = pm_services.post_id
|
||||
INNER JOIN {$wpdb->postmeta} pm_in ON p.ID = pm_in.post_id
|
||||
INNER JOIN {$wpdb->postmeta} pm_status ON p.ID = pm_status.post_id
|
||||
WHERE p.post_type = %s
|
||||
AND p.post_status = 'publish'
|
||||
AND pm_services.meta_key = '_bnb_booking_services_total'
|
||||
AND pm_in.meta_key = '_bnb_booking_check_in'
|
||||
AND pm_in.meta_value >= %s
|
||||
AND pm_status.meta_key = '_bnb_booking_status'
|
||||
AND pm_status.meta_value IN ('confirmed', 'checked_in', 'checked_out')",
|
||||
Booking::POST_TYPE,
|
||||
$first_of_month
|
||||
)
|
||||
);
|
||||
|
||||
return round( (float) $result, 2 );
|
||||
}
|
||||
}
|
||||
184
src/Plugin.php
184
src/Plugin.php
@@ -19,6 +19,7 @@ use Magdev\WpBnb\Booking\EmailNotifier;
|
||||
use Magdev\WpBnb\Frontend\Search;
|
||||
use Magdev\WpBnb\Frontend\Shortcodes;
|
||||
use Magdev\WpBnb\Integration\CF7;
|
||||
use Magdev\WpBnb\Integration\Prometheus;
|
||||
use Magdev\WpBnb\Frontend\Widgets\AvailabilityCalendar;
|
||||
use Magdev\WpBnb\Frontend\Widgets\BuildingRooms;
|
||||
use Magdev\WpBnb\Frontend\Widgets\SimilarRooms;
|
||||
@@ -142,6 +143,9 @@ final class Plugin {
|
||||
CF7::init();
|
||||
}
|
||||
|
||||
// Initialize Prometheus metrics integration.
|
||||
Prometheus::init();
|
||||
|
||||
// Initialize admin components.
|
||||
if ( is_admin() ) {
|
||||
$this->init_admin();
|
||||
@@ -610,6 +614,10 @@ final class Plugin {
|
||||
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>
|
||||
</nav>
|
||||
|
||||
<div class="tab-content">
|
||||
@@ -624,6 +632,9 @@ final class Plugin {
|
||||
case 'updates':
|
||||
$this->render_updates_settings();
|
||||
break;
|
||||
case 'metrics':
|
||||
$this->render_metrics_settings();
|
||||
break;
|
||||
default:
|
||||
$this->render_general_settings();
|
||||
break;
|
||||
@@ -1265,6 +1276,163 @@ final class Plugin {
|
||||
<?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 license status badge.
|
||||
*
|
||||
@@ -1336,6 +1504,9 @@ final class Plugin {
|
||||
case 'updates':
|
||||
$this->save_updates_settings();
|
||||
break;
|
||||
case 'metrics':
|
||||
$this->save_metrics_settings();
|
||||
break;
|
||||
default:
|
||||
$this->save_general_settings();
|
||||
break;
|
||||
@@ -1465,6 +1636,19 @@ final class Plugin {
|
||||
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' );
|
||||
}
|
||||
|
||||
/**
|
||||
* AJAX handler for checking room availability.
|
||||
*
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* Plugin Name: WP BnB Management
|
||||
* Plugin URI: https://src.bundespruefstelle.ch/magdev/wp-bnb
|
||||
* Description: A comprehensive Bed & Breakfast management system for WordPress. Manage buildings, rooms, bookings, and guests.
|
||||
* Version: 0.8.0
|
||||
* Version: 0.9.0
|
||||
* Requires at least: 6.0
|
||||
* Requires PHP: 8.3
|
||||
* Author: Marco Graetsch
|
||||
@@ -24,7 +24,7 @@ if ( ! defined( 'ABSPATH' ) ) {
|
||||
}
|
||||
|
||||
// Plugin version constant - MUST match Version in header above.
|
||||
define( 'WP_BNB_VERSION', '0.8.0' );
|
||||
define( 'WP_BNB_VERSION', '0.9.0' );
|
||||
|
||||
// Plugin path constants.
|
||||
define( 'WP_BNB_PATH', plugin_dir_path( __FILE__ ) );
|
||||
|
||||
Reference in New Issue
Block a user