4 Commits

Author SHA1 Message Date
dbd0f3f788 Security audit and bug fixes (v0.12.0)
All checks were successful
Create Release Package / build-release (push) Successful in 1m37s
- Complete security audit for WordPress best practices, OWASP Top 10
- Fix Calculator static method calls in API controllers
- Fix EmailNotifier method names in BookingsController
- Fix guest_id type casting in EmailNotifier

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-04 09:44:23 +01:00
a8e0df99d1 Update CLAUDE.md with v0.11.2/v0.11.3 session history
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-03 23:41:43 +01:00
70d588808e Display calendar filters side by side (v0.11.3)
All checks were successful
Create Release Package / build-release (push) Successful in 1m8s
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-03 23:40:05 +01:00
0bf7f19ac5 Improve calendar room column with building name display (v0.11.2)
All checks were successful
Create Release Package / build-release (push) Successful in 1m1s
- Widen room column to 200px with proper left alignment
- Display building name as second row under room name
- Change table-layout from fixed to auto for flexible columns

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-03 23:31:45 +01:00
10 changed files with 201 additions and 35 deletions

View File

@@ -5,6 +5,43 @@ 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.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
### Changed
- Calendar filters now display side by side instead of stacked rows
## [0.11.2] - 2026-02-03
### Changed
- Calendar page room column now wider (200px) with proper left alignment
- Room column displays building name on second row for better identification
- Changed calendar table layout from fixed to auto for flexible column widths
## [0.11.1] - 2026-02-03 ## [0.11.1] - 2026-02-03
### Added ### Added

101
CLAUDE.md
View File

@@ -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
View File

@@ -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 |

View File

@@ -950,13 +950,17 @@
/* Calendar Filters */ /* Calendar Filters */
.bnb-calendar-filters { .bnb-calendar-filters {
display: flex;
gap: 15px;
padding: 15px 20px; padding: 15px 20px;
border-bottom: 1px solid #c3c4c7; border-bottom: 1px solid #c3c4c7;
background: #f9f9f9; background: #f9f9f9;
} }
.bnb-calendar-filters form {
display: flex;
align-items: center;
gap: 20px;
}
.bnb-calendar-filters label { .bnb-calendar-filters label {
display: flex; display: flex;
align-items: center; align-items: center;
@@ -976,7 +980,7 @@
.bnb-calendar-table { .bnb-calendar-table {
width: 100%; width: 100%;
border-collapse: collapse; border-collapse: collapse;
table-layout: fixed; table-layout: auto;
} }
.bnb-calendar-table th, .bnb-calendar-table th,
@@ -997,7 +1001,8 @@
.bnb-calendar-table th.room-header { .bnb-calendar-table th.room-header {
text-align: left; text-align: left;
padding-left: 10px; padding-left: 10px;
min-width: 150px; width: 200px;
min-width: 200px;
} }
/* Calendar Day Cell */ /* Calendar Day Cell */
@@ -1068,16 +1073,31 @@
} }
/* Room Row in Multi-Room Calendar */ /* Room Row in Multi-Room Calendar */
.bnb-calendar-room { .bnb-calendar-table .bnb-calendar-room {
font-weight: 600; font-weight: 600;
text-align: left; text-align: left;
padding: 8px 10px; padding: 8px 10px;
background: #f6f7f7; background: #f6f7f7;
min-width: 200px;
white-space: nowrap;
} }
.bnb-calendar-room small { .bnb-calendar-room a {
display: block;
}
.bnb-calendar-room .room-number {
font-weight: normal; font-weight: normal;
color: #646970; color: #646970;
margin-left: 5px;
}
.bnb-calendar-room .building-name {
display: block;
font-weight: normal;
font-size: 12px;
color: #646970;
margin-top: 2px;
} }
/* Calendar Legend */ /* Calendar Legend */
@@ -1157,8 +1177,9 @@
/* Responsive */ /* Responsive */
@media screen and (max-width: 782px) { @media screen and (max-width: 782px) {
.bnb-calendar-filters { .bnb-calendar-filters form {
flex-direction: column; flex-direction: column;
align-items: stretch;
} }
.bnb-calendar-filters select { .bnb-calendar-filters select {

View File

@@ -256,16 +256,20 @@ final class Calendar {
<tbody> <tbody>
<?php foreach ( $rooms as $room ) : ?> <?php foreach ( $rooms as $room ) : ?>
<?php <?php
$room_number = get_post_meta( $room->ID, '_bnb_room_room_number', true ); $room_number = get_post_meta( $room->ID, '_bnb_room_room_number', true );
$booked_dates = Availability::get_booked_dates( $room->ID, $year, $month ); $room_building = Room::get_building( $room->ID );
$booked_dates = Availability::get_booked_dates( $room->ID, $year, $month );
?> ?>
<tr> <tr>
<td class="bnb-calendar-room"> <td class="bnb-calendar-room">
<a href="<?php echo esc_url( get_edit_post_link( $room->ID ) ); ?>"> <a href="<?php echo esc_url( get_edit_post_link( $room->ID ) ); ?>">
<?php echo esc_html( $room->post_title ); ?> <?php echo esc_html( $room->post_title ); ?>
<?php if ( $room_number ) : ?>
<span class="room-number">#<?php echo esc_html( $room_number ); ?></span>
<?php endif; ?>
</a> </a>
<?php if ( $room_number ) : ?> <?php if ( $room_building ) : ?>
<br><small>#<?php echo esc_html( $room_number ); ?></small> <span class="building-name"><?php echo esc_html( $room_building->post_title ); ?></span>
<?php endif; ?> <?php endif; ?>
</td> </td>
<?php for ( $day = 1; $day <= $days_in_month; $day++ ) : ?> <?php for ( $day = 1; $day <= $days_in_month; $day++ ) : ?>

View File

@@ -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 );
$services = $request->get_param( 'services' ) ?? array(); $room_price = $calculator->calculate();
$room_price = $price['price'] ?? 0; $services = $request->get_param( 'services' ) ?? array();
// 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 );
} }
} }

View File

@@ -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.

View File

@@ -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.

View File

@@ -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 );

View File

@@ -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.1 * Version: 0.12.0
* 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.1' ); define( 'WP_BNB_VERSION', '0.12.0' );
// Plugin path constants. // Plugin path constants.
define( 'WP_BNB_PATH', plugin_dir_path( __FILE__ ) ); define( 'WP_BNB_PATH', plugin_dir_path( __FILE__ ) );