Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d39abc0dd1 | |||
| a1155af6a0 | |||
| 23f073339a | |||
| c92be303e8 | |||
| ada838a1e4 | |||
| dbd0f3f788 | |||
| a8e0df99d1 |
36
CHANGELOG.md
36
CHANGELOG.md
@@ -5,6 +5,42 @@ 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/),
|
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).
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
|
## [0.12.1] - 2026-02-04
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Fixed WooCommerce product sync button not working on settings page
|
||||||
|
- Fixed AJAX handler registration timing (now registered directly instead of on `woocommerce_loaded` hook)
|
||||||
|
- Fixed sync button icon vertical alignment
|
||||||
|
- Added Dashboard link to WordPress plugins list page
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Better error handling and console logging for WooCommerce sync AJAX requests
|
||||||
|
|
||||||
|
## [0.12.0] - 2026-02-04
|
||||||
|
|
||||||
|
### Security
|
||||||
|
|
||||||
|
- Completed comprehensive security audit (Phase 12)
|
||||||
|
- Verified WordPress best practices compliance across entire codebase
|
||||||
|
- Confirmed protection against SQL Injection: all database queries use `$wpdb->prepare()` or WP_Query
|
||||||
|
- Confirmed protection against XSS: all output properly escaped with `esc_html()`, `esc_attr()`, `esc_url()`
|
||||||
|
- Confirmed protection against CSRF: nonce verification on all forms and admin AJAX handlers
|
||||||
|
- Verified REST API endpoint security: proper permission callbacks, rate limiting, input sanitization
|
||||||
|
- Sensitive data (ID/passport numbers) properly encrypted and not exposed via API
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Fixed Calculator being called statically in API controllers (`BookingsController`, `RoomsController`, `PricingController`)
|
||||||
|
- Fixed EmailNotifier method names in BookingsController (`send_admin_new_booking`, `send_cancellation`, `send_guest_confirmation`)
|
||||||
|
- Fixed guest_id type casting in EmailNotifier (string to int from post meta)
|
||||||
|
|
||||||
|
### Notes
|
||||||
|
|
||||||
|
- Public AJAX endpoints (search, availability, calendar, price calculation) intentionally do not require nonce verification as they are read-only public APIs with proper input sanitization
|
||||||
|
- All admin AJAX endpoints properly protected with nonce verification and capability checks
|
||||||
|
|
||||||
## [0.11.3] - 2026-02-03
|
## [0.11.3] - 2026-02-03
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|||||||
101
CLAUDE.md
101
CLAUDE.md
@@ -1234,3 +1234,104 @@ Admin features always work; frontend requires valid license.
|
|||||||
- Merged to main (fast-forward)
|
- Merged to main (fast-forward)
|
||||||
- Tagged: `v0.11.0`
|
- Tagged: `v0.11.0`
|
||||||
- Pushed to origin: dev, main, v0.11.0
|
- Pushed to origin: dev, main, v0.11.0
|
||||||
|
|
||||||
|
|
||||||
|
### 2026-02-03 - Version 0.11.2/0.11.3 (Calendar UI Improvements)
|
||||||
|
|
||||||
|
**Completed:**
|
||||||
|
|
||||||
|
- Improved Calendar admin page room column
|
||||||
|
- Increased room column width from narrow to 200px minimum
|
||||||
|
- Changed `table-layout` from `fixed` to `auto` for flexible column sizing
|
||||||
|
- Added building name as second row under room name
|
||||||
|
- Left-aligned room column content for better readability
|
||||||
|
- Improved Calendar filter layout
|
||||||
|
- Changed filter form to flexbox layout (side by side instead of stacked)
|
||||||
|
- Added gap between filter dropdowns
|
||||||
|
- Updated responsive styles for smaller screens
|
||||||
|
|
||||||
|
**Files Changed:**
|
||||||
|
|
||||||
|
- `src/Admin/Calendar.php` - Added building name display in room cell
|
||||||
|
- `assets/css/admin.css` - Calendar table and filter layout improvements
|
||||||
|
- `wp-bnb.php` - Version bumps to 0.11.2 and 0.11.3
|
||||||
|
- `CHANGELOG.md` - Added v0.11.2 and v0.11.3 release notes
|
||||||
|
|
||||||
|
**Learnings:**
|
||||||
|
|
||||||
|
- CSS `table-layout: fixed` forces equal column widths; use `auto` for content-based sizing
|
||||||
|
- When a parent container has flexbox but content is in a child element, the flex must be applied to the correct container (form element in this case)
|
||||||
|
- Higher CSS specificity (`.bnb-calendar-table .bnb-calendar-room`) needed to override inherited styles
|
||||||
|
|
||||||
|
**Released:**
|
||||||
|
|
||||||
|
- v0.11.2: Calendar room column width and building name display
|
||||||
|
- v0.11.3: Calendar filters side-by-side layout
|
||||||
|
- Both versions tagged and pushed to origin
|
||||||
|
|
||||||
|
### 2026-02-04 - Version 0.12.0 (Security Audit)
|
||||||
|
|
||||||
|
**Completed:**
|
||||||
|
|
||||||
|
- Comprehensive security audit (Phase 12)
|
||||||
|
- WordPress best practices compliance verification
|
||||||
|
- OWASP Top 10 vulnerability review
|
||||||
|
- Live API endpoint testing against localhost:9080
|
||||||
|
|
||||||
|
**Security Audit Findings:**
|
||||||
|
|
||||||
|
1. **SQL Injection:** ✓ PROTECTED
|
||||||
|
- All `$wpdb` queries use `$wpdb->prepare()` with parameterized queries
|
||||||
|
- WP_Query used throughout for post queries (inherently safe)
|
||||||
|
- Format specifiers (`%s`, `%d`) properly used in `$wpdb->update()` calls
|
||||||
|
|
||||||
|
2. **XSS (Cross-Site Scripting):** ✓ PROTECTED
|
||||||
|
- PHP output consistently uses `esc_html()`, `esc_attr()`, `esc_url()`
|
||||||
|
- JavaScript uses `escapeHtml()` function (textContent/innerHTML pattern)
|
||||||
|
- Form values properly escaped before output
|
||||||
|
|
||||||
|
3. **CSRF (Cross-Site Request Forgery):** ✓ PROTECTED
|
||||||
|
- All forms use `wp_nonce_field()`
|
||||||
|
- Admin AJAX handlers use `check_ajax_referer()` or `wp_verify_nonce()`
|
||||||
|
- Capability checks with `current_user_can()` on privileged operations
|
||||||
|
- Public AJAX endpoints are read-only and don't modify data
|
||||||
|
|
||||||
|
4. **REST API Security:** ✓ PROTECTED
|
||||||
|
- Permission callbacks on all admin endpoints (`admin_permission`)
|
||||||
|
- Rate limiting via transient-based `RateLimiter` class
|
||||||
|
- Input sanitization via `sanitize_callback` on all parameters
|
||||||
|
- Sensitive data (ID/passport) not exposed via API
|
||||||
|
|
||||||
|
5. **Data Encryption:** ✓ IMPLEMENTED
|
||||||
|
- Guest ID/passport numbers encrypted with AES-256-CBC
|
||||||
|
- IV stored with encrypted data for secure decryption
|
||||||
|
|
||||||
|
**Bugs Fixed During Audit:**
|
||||||
|
|
||||||
|
- Fixed `Calculator::calculate()` being called statically (non-static method)
|
||||||
|
- `src/Api/Controllers/BookingsController.php`
|
||||||
|
- `src/Api/Controllers/RoomsController.php`
|
||||||
|
- `src/Api/Controllers/PricingController.php`
|
||||||
|
- Fixed incorrect EmailNotifier method names in BookingsController
|
||||||
|
- `send_admin_notification` → `send_admin_new_booking`
|
||||||
|
- `send_cancellation_email` → `send_cancellation`
|
||||||
|
- `send_confirmation_email` → `send_guest_confirmation`
|
||||||
|
- Fixed guest_id type casting in EmailNotifier (string from post meta → int)
|
||||||
|
|
||||||
|
**Files Changed:**
|
||||||
|
|
||||||
|
- `src/Api/Controllers/BookingsController.php` - Calculator instantiation, EmailNotifier method names
|
||||||
|
- `src/Api/Controllers/RoomsController.php` - Calculator instantiation
|
||||||
|
- `src/Api/Controllers/PricingController.php` - Calculator instantiation
|
||||||
|
- `src/Booking/EmailNotifier.php` - guest_id type casting
|
||||||
|
- `wp-bnb.php` - Version bump to 0.12.0
|
||||||
|
- `CHANGELOG.md` - Added v0.12.0 security audit notes
|
||||||
|
- `PLAN.md` - Marked Phase 12 complete
|
||||||
|
|
||||||
|
**Learnings:**
|
||||||
|
|
||||||
|
- `get_post_meta()` always returns strings; cast to `(int)` when needed for type-hinted methods
|
||||||
|
- Static vs instance method calls must match the method declaration
|
||||||
|
- Public frontend AJAX endpoints can safely skip nonce verification if they're read-only
|
||||||
|
- Rate limiting headers (`X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset`) provide client feedback
|
||||||
|
- WordPress REST API permission callbacks should use capability checks, not user login status alone
|
||||||
|
|||||||
11
PLAN.md
11
PLAN.md
@@ -211,11 +211,12 @@ This document outlines the implementation plan for the WP BnB Management plugin.
|
|||||||
- [x] Order management
|
- [x] Order management
|
||||||
- [x] Refund handling
|
- [x] Refund handling
|
||||||
|
|
||||||
## Phase 12: Security Audit (v0.12.0)
|
## Phase 12: Security Audit (v0.12.0) - Complete
|
||||||
|
|
||||||
- [ ] Check for Wordpress best-practices
|
- [x] Check for WordPress best-practices
|
||||||
- [ ] Review the code for OWASP Top 10, including XSS, XSRF, SQLi and other critical threads
|
- [x] Review the code for OWASP Top 10, including XSS, CSRF, SQLi and other critical threats
|
||||||
- [ ] Test the API-Endpoints against a local live system under <http://localhost:9080/> for common vulnerabilities
|
- [x] Test the API-Endpoints against a local live system under <http://localhost:9080/> for common vulnerabilities
|
||||||
|
- [x] Fix bugs discovered during security audit
|
||||||
|
|
||||||
## Future Considerations (v1.0.0+)
|
## Future Considerations (v1.0.0+)
|
||||||
|
|
||||||
@@ -360,5 +361,5 @@ The plugin will provide extensive hooks for customization:
|
|||||||
| 0.9.0 | Prometheus Metrics | Complete |
|
| 0.9.0 | Prometheus Metrics | Complete |
|
||||||
| 0.10.0 | API Endpoints | Complete |
|
| 0.10.0 | API Endpoints | Complete |
|
||||||
| 0.11.0 | WooCommerce Integration | Complete |
|
| 0.11.0 | WooCommerce Integration | Complete |
|
||||||
| 0.12.0 | Security Audit | TBD |
|
| 0.12.0 | Security Audit | Complete |
|
||||||
| 1.0.0 | Stable Release | TBD |
|
| 1.0.0 | Stable Release | TBD |
|
||||||
|
|||||||
@@ -2098,3 +2098,49 @@
|
|||||||
width: 16px;
|
width: 16px;
|
||||||
height: 16px;
|
height: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ============================================
|
||||||
|
WooCommerce Sync Button
|
||||||
|
============================================ */
|
||||||
|
.bnb-sync-rooms-btn {
|
||||||
|
display: inline-flex !important;
|
||||||
|
align-items: center !important;
|
||||||
|
gap: 6px;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bnb-sync-rooms-btn .dashicons {
|
||||||
|
font-size: 16px;
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
line-height: 16px;
|
||||||
|
display: inline-block;
|
||||||
|
vertical-align: middle;
|
||||||
|
margin-top: -2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bnb-sync-rooms-btn .dashicons.bnb-spin {
|
||||||
|
animation: bnb-spin 1s linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes bnb-spin {
|
||||||
|
from {
|
||||||
|
transform: rotate(0deg);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
transform: rotate(360deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.sync-status {
|
||||||
|
margin-left: 10px;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sync-status .bnb-sync-success {
|
||||||
|
color: #00a32a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sync-status .bnb-sync-error {
|
||||||
|
color: #d63638;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1199,6 +1199,59 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize WooCommerce sync button.
|
||||||
|
*/
|
||||||
|
function initWooCommerceSync() {
|
||||||
|
var $syncBtn = $('.bnb-sync-rooms-btn');
|
||||||
|
|
||||||
|
if (!$syncBtn.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$syncBtn.on('click', function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
var $btn = $(this);
|
||||||
|
var $status = $btn.parent().find('.sync-status');
|
||||||
|
|
||||||
|
$btn.prop('disabled', true);
|
||||||
|
$btn.find('.dashicons').addClass('bnb-spin');
|
||||||
|
$status.text(wpBnbAdmin.i18n.syncing || 'Syncing...');
|
||||||
|
|
||||||
|
$.ajax({
|
||||||
|
url: wpBnbAdmin.ajaxUrl,
|
||||||
|
type: 'POST',
|
||||||
|
data: {
|
||||||
|
action: 'wp_bnb_sync_all_rooms',
|
||||||
|
nonce: wpBnbAdmin.nonce
|
||||||
|
},
|
||||||
|
success: function(response) {
|
||||||
|
if (response.success) {
|
||||||
|
$status.html('<span class="bnb-sync-success">' + response.data.message + '</span>');
|
||||||
|
} else {
|
||||||
|
var errorMsg = response.data && response.data.message ? response.data.message : 'Unknown error';
|
||||||
|
$status.html('<span class="bnb-sync-error">' + errorMsg + '</span>');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error: function(xhr, status, error) {
|
||||||
|
var errorMsg = wpBnbAdmin.i18n.error || 'Error occurred';
|
||||||
|
if (xhr.responseJSON && xhr.responseJSON.data && xhr.responseJSON.data.message) {
|
||||||
|
errorMsg = xhr.responseJSON.data.message;
|
||||||
|
} else if (error) {
|
||||||
|
errorMsg = error;
|
||||||
|
}
|
||||||
|
$status.html('<span class="bnb-sync-error">' + errorMsg + '</span>');
|
||||||
|
console.error('WP-BnB Sync Error:', status, error, xhr.responseText);
|
||||||
|
},
|
||||||
|
complete: function() {
|
||||||
|
$btn.prop('disabled', false);
|
||||||
|
$btn.find('.dashicons').removeClass('bnb-spin');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Initialize on document ready.
|
// Initialize on document ready.
|
||||||
$(document).ready(function() {
|
$(document).ready(function() {
|
||||||
initLicenseManagement();
|
initLicenseManagement();
|
||||||
@@ -1214,6 +1267,7 @@
|
|||||||
initBookingServices();
|
initBookingServices();
|
||||||
initDashboardCharts();
|
initDashboardCharts();
|
||||||
initReportsPage();
|
initReportsPage();
|
||||||
|
initWooCommerceSync();
|
||||||
});
|
});
|
||||||
|
|
||||||
})(jQuery);
|
})(jQuery);
|
||||||
|
|||||||
@@ -338,9 +338,9 @@ final class BookingsController extends AbstractController {
|
|||||||
$guest_id = $this->find_or_create_guest( $guest );
|
$guest_id = $this->find_or_create_guest( $guest );
|
||||||
|
|
||||||
// Calculate price.
|
// Calculate price.
|
||||||
$price = Calculator::calculate( $room_id, $check_in, $check_out );
|
$calculator = new Calculator( $room_id, $check_in, $check_out );
|
||||||
|
$room_price = $calculator->calculate();
|
||||||
$services = $request->get_param( 'services' ) ?? array();
|
$services = $request->get_param( 'services' ) ?? array();
|
||||||
$room_price = $price['price'] ?? 0;
|
|
||||||
|
|
||||||
// Calculate services total.
|
// Calculate services total.
|
||||||
$services_total = 0;
|
$services_total = 0;
|
||||||
@@ -410,7 +410,7 @@ final class BookingsController extends AbstractController {
|
|||||||
|
|
||||||
// Send notification email.
|
// Send notification email.
|
||||||
if ( class_exists( EmailNotifier::class ) ) {
|
if ( class_exists( EmailNotifier::class ) ) {
|
||||||
EmailNotifier::send_admin_notification( $post_id );
|
EmailNotifier::send_admin_new_booking( $post_id );
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prepare response.
|
// Prepare response.
|
||||||
@@ -532,7 +532,7 @@ final class BookingsController extends AbstractController {
|
|||||||
|
|
||||||
// Send cancellation email.
|
// Send cancellation email.
|
||||||
if ( class_exists( EmailNotifier::class ) ) {
|
if ( class_exists( EmailNotifier::class ) ) {
|
||||||
EmailNotifier::send_cancellation_email( $id );
|
EmailNotifier::send_cancellation( $id );
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->formatter->no_content();
|
return $this->formatter->no_content();
|
||||||
@@ -607,7 +607,7 @@ final class BookingsController extends AbstractController {
|
|||||||
if ( 'confirmed' === $new_status ) {
|
if ( 'confirmed' === $new_status ) {
|
||||||
update_post_meta( $id, '_bnb_booking_confirmed_at', current_time( 'mysql' ) );
|
update_post_meta( $id, '_bnb_booking_confirmed_at', current_time( 'mysql' ) );
|
||||||
if ( class_exists( EmailNotifier::class ) ) {
|
if ( class_exists( EmailNotifier::class ) ) {
|
||||||
EmailNotifier::send_confirmation_email( $id );
|
EmailNotifier::send_guest_confirmation( $id );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -136,9 +136,9 @@ final class PricingController extends AbstractController {
|
|||||||
$nights = (int) $check_in_date->diff( $check_out_date )->days;
|
$nights = (int) $check_in_date->diff( $check_out_date )->days;
|
||||||
|
|
||||||
// Calculate room price.
|
// Calculate room price.
|
||||||
$price = Calculator::calculate( $room_id, $check_in, $check_out );
|
$calculator = new Calculator( $room_id, $check_in, $check_out );
|
||||||
$room_total = $price['price'] ?? 0;
|
$room_total = $calculator->calculate();
|
||||||
$breakdown = $price['breakdown'] ?? array();
|
$breakdown = $calculator->getBreakdown();
|
||||||
$currency = get_option( 'wp_bnb_currency', 'CHF' );
|
$currency = get_option( 'wp_bnb_currency', 'CHF' );
|
||||||
|
|
||||||
// Build night-by-night breakdown.
|
// Build night-by-night breakdown.
|
||||||
|
|||||||
@@ -400,14 +400,16 @@ final class RoomsController extends AbstractController {
|
|||||||
|
|
||||||
if ( $is_available ) {
|
if ( $is_available ) {
|
||||||
// Calculate pricing.
|
// Calculate pricing.
|
||||||
$price = Calculator::calculate( $room_id, $check_in, $check_out );
|
$calculator = new Calculator( $room_id, $check_in, $check_out );
|
||||||
|
$price = $calculator->calculate();
|
||||||
|
$breakdown = $calculator->getBreakdown();
|
||||||
$data['pricing'] = array(
|
$data['pricing'] = array(
|
||||||
'tier' => $price['breakdown']['tier']->value ?? 'short_term',
|
'tier' => $breakdown['tier']->value ?? 'short_term',
|
||||||
'base_rate' => $price['breakdown']['base_price_per_night'] ?? 0,
|
'base_rate' => $breakdown['base_price_per_night'] ?? 0,
|
||||||
'total' => $price['price'] ?? 0,
|
'total' => $price,
|
||||||
'formatted' => $price['price_formatted'] ?? '',
|
'formatted' => Calculator::formatPrice( $price ),
|
||||||
'currency' => get_option( 'wp_bnb_currency', 'CHF' ),
|
'currency' => get_option( 'wp_bnb_currency', 'CHF' ),
|
||||||
'breakdown' => $price['breakdown'] ?? array(),
|
'breakdown' => $breakdown,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
// Get conflicts.
|
// Get conflicts.
|
||||||
|
|||||||
@@ -263,10 +263,10 @@ final class EmailNotifier {
|
|||||||
* @return array Guest data with keys: name, first_name, last_name, email, phone, notes, full_address.
|
* @return array Guest data with keys: name, first_name, last_name, email, phone, notes, full_address.
|
||||||
*/
|
*/
|
||||||
private static function get_guest_data( int $booking_id ): array {
|
private static function get_guest_data( int $booking_id ): array {
|
||||||
$guest_id = get_post_meta( $booking_id, '_bnb_booking_guest_id', true );
|
$guest_id = (int) get_post_meta( $booking_id, '_bnb_booking_guest_id', true );
|
||||||
|
|
||||||
// Try to get data from Guest CPT.
|
// Try to get data from Guest CPT.
|
||||||
if ( $guest_id ) {
|
if ( $guest_id > 0 ) {
|
||||||
$guest = get_post( $guest_id );
|
$guest = get_post( $guest_id );
|
||||||
if ( $guest && Guest::POST_TYPE === $guest->post_type ) {
|
if ( $guest && Guest::POST_TYPE === $guest->post_type ) {
|
||||||
$first_name = get_post_meta( $guest_id, '_bnb_guest_first_name', true );
|
$first_name = get_post_meta( $guest_id, '_bnb_guest_first_name', true );
|
||||||
|
|||||||
@@ -130,7 +130,11 @@ final class Manager {
|
|||||||
// Declare HPOS compatibility.
|
// Declare HPOS compatibility.
|
||||||
add_action( 'before_woocommerce_init', array( self::class, 'declare_hpos_compatibility' ) );
|
add_action( 'before_woocommerce_init', array( self::class, 'declare_hpos_compatibility' ) );
|
||||||
|
|
||||||
// Only initialize components if integration is enabled.
|
// Always register admin AJAX handlers immediately (for settings page).
|
||||||
|
// Must be called directly, not via hook, since woocommerce_loaded may have already fired.
|
||||||
|
ProductSync::init_admin_ajax();
|
||||||
|
|
||||||
|
// Only initialize full components if integration is enabled.
|
||||||
if ( ! self::is_enabled() ) {
|
if ( ! self::is_enabled() ) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,6 +25,13 @@ use Magdev\WpBnb\Taxonomies\RoomType;
|
|||||||
*/
|
*/
|
||||||
final class ProductSync {
|
final class ProductSync {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Track if admin AJAX has been initialized.
|
||||||
|
*
|
||||||
|
* @var bool
|
||||||
|
*/
|
||||||
|
private static bool $admin_ajax_initialized = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialize product synchronization.
|
* Initialize product synchronization.
|
||||||
*
|
*
|
||||||
@@ -40,8 +47,27 @@ final class ProductSync {
|
|||||||
// Add linked room info to product edit screen.
|
// Add linked room info to product edit screen.
|
||||||
add_action( 'woocommerce_product_options_general_product_data', array( self::class, 'add_product_room_info' ) );
|
add_action( 'woocommerce_product_options_general_product_data', array( self::class, 'add_product_room_info' ) );
|
||||||
|
|
||||||
|
// Register admin AJAX if not already done.
|
||||||
|
self::init_admin_ajax();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize admin AJAX handlers only.
|
||||||
|
*
|
||||||
|
* Called separately from init() to ensure AJAX is available on settings page
|
||||||
|
* even when full integration is not yet enabled.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public static function init_admin_ajax(): void {
|
||||||
|
if ( self::$admin_ajax_initialized ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// AJAX handler for syncing all rooms.
|
// AJAX handler for syncing all rooms.
|
||||||
add_action( 'wp_ajax_wp_bnb_sync_all_rooms', array( self::class, 'ajax_sync_all_rooms' ) );
|
add_action( 'wp_ajax_wp_bnb_sync_all_rooms', array( self::class, 'ajax_sync_all_rooms' ) );
|
||||||
|
|
||||||
|
self::$admin_ajax_initialized = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -350,6 +350,8 @@ final class Plugin {
|
|||||||
'checkingUpdates' => __( 'Checking for updates...', 'wp-bnb' ),
|
'checkingUpdates' => __( 'Checking for updates...', 'wp-bnb' ),
|
||||||
'occupancy' => __( 'Occupancy %', 'wp-bnb' ),
|
'occupancy' => __( 'Occupancy %', 'wp-bnb' ),
|
||||||
'revenue' => __( 'Revenue', 'wp-bnb' ),
|
'revenue' => __( 'Revenue', 'wp-bnb' ),
|
||||||
|
'syncing' => __( 'Syncing...', 'wp-bnb' ),
|
||||||
|
'syncComplete' => __( 'Sync complete', 'wp-bnb' ),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
24
wp-bnb.php
24
wp-bnb.php
@@ -3,7 +3,7 @@
|
|||||||
* Plugin Name: WP BnB Management
|
* Plugin Name: WP BnB Management
|
||||||
* Plugin URI: https://src.bundespruefstelle.ch/magdev/wp-bnb
|
* Plugin URI: https://src.bundespruefstelle.ch/magdev/wp-bnb
|
||||||
* Description: A comprehensive Bed & Breakfast management system for WordPress. Manage buildings, rooms, bookings, and guests.
|
* Description: A comprehensive Bed & Breakfast management system for WordPress. Manage buildings, rooms, bookings, and guests.
|
||||||
* Version: 0.11.3
|
* Version: 0.12.1
|
||||||
* Requires at least: 6.0
|
* Requires at least: 6.0
|
||||||
* Requires PHP: 8.3
|
* Requires PHP: 8.3
|
||||||
* Author: Marco Graetsch
|
* Author: Marco Graetsch
|
||||||
@@ -24,7 +24,7 @@ if ( ! defined( 'ABSPATH' ) ) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Plugin version constant - MUST match Version in header above.
|
// Plugin version constant - MUST match Version in header above.
|
||||||
define( 'WP_BNB_VERSION', '0.11.3' );
|
define( 'WP_BNB_VERSION', '0.12.1' );
|
||||||
|
|
||||||
// Plugin path constants.
|
// Plugin path constants.
|
||||||
define( 'WP_BNB_PATH', plugin_dir_path( __FILE__ ) );
|
define( 'WP_BNB_PATH', plugin_dir_path( __FILE__ ) );
|
||||||
@@ -189,3 +189,23 @@ function wp_bnb_deactivate(): void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
register_deactivation_hook( __FILE__, 'wp_bnb_deactivate' );
|
register_deactivation_hook( __FILE__, 'wp_bnb_deactivate' );
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add action links to the plugins page.
|
||||||
|
*
|
||||||
|
* @param array $links Existing plugin action links.
|
||||||
|
* @return array Modified plugin action links.
|
||||||
|
*/
|
||||||
|
function wp_bnb_plugin_action_links( array $links ): array {
|
||||||
|
$dashboard_link = sprintf(
|
||||||
|
'<a href="%s">%s</a>',
|
||||||
|
esc_url( admin_url( 'admin.php?page=wp-bnb' ) ),
|
||||||
|
esc_html__( 'Dashboard', 'wp-bnb' )
|
||||||
|
);
|
||||||
|
|
||||||
|
array_unshift( $links, $dashboard_link );
|
||||||
|
|
||||||
|
return $links;
|
||||||
|
}
|
||||||
|
|
||||||
|
add_filter( 'plugin_action_links_' . WP_BNB_BASENAME, 'wp_bnb_plugin_action_links' );
|
||||||
|
|||||||
Reference in New Issue
Block a user