Release v0.6.1 - Bug fixes and enhancements
All checks were successful
Create Release Package / build-release (push) Successful in 1m1s
All checks were successful
Create Release Package / build-release (push) Successful in 1m1s
New Features: - Auto-update system with configurable check frequency - Updates tab in settings with manual check button - Localhost development mode bypasses license validation - Extended general settings (address, contact, social media) - Pricing settings split into subtabs - Guest ID/passport encryption using AES-256-CBC - Guest auto-creation from booking form Bug Fixes: - Fixed Booking admin issues with auto-draft status - Fixed guest dropdown loading in booking form - Fixed booking history display on Guest edit page - Fixed service pricing meta box (Gutenberg hiding meta boxes) Changes: - Admin submenu reordered for better organization - Booking title shows guest name and dates (room removed) - Service, Guest, Booking use classic editor (not Gutenberg) - Settings tabs flush with content (no gap) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -54,8 +54,24 @@ final class Booking {
|
||||
add_action( 'restrict_manage_posts', array( self::class, 'add_filters' ) );
|
||||
add_action( 'pre_get_posts', array( self::class, 'filter_query' ) );
|
||||
add_filter( 'enter_title_here', array( self::class, 'change_title_placeholder' ), 10, 2 );
|
||||
add_filter( 'wp_insert_post_data', array( self::class, 'auto_generate_title' ), 10, 2 );
|
||||
add_action( 'admin_notices', array( self::class, 'show_conflict_notice' ) );
|
||||
|
||||
// Disable Gutenberg block editor for Bookings - use classic editor for form-based UI.
|
||||
add_filter( 'use_block_editor_for_post_type', array( self::class, 'disable_block_editor' ), 10, 2 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable block editor for Bookings post type.
|
||||
*
|
||||
* @param bool $use_block_editor Whether to use block editor.
|
||||
* @param string $post_type Post type.
|
||||
* @return bool
|
||||
*/
|
||||
public static function disable_block_editor( bool $use_block_editor, string $post_type ): bool {
|
||||
if ( self::POST_TYPE === $post_type ) {
|
||||
return false;
|
||||
}
|
||||
return $use_block_editor;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -296,7 +312,7 @@ final class Booking {
|
||||
* @return void
|
||||
*/
|
||||
public static function render_guest_meta_box( \WP_Post $post ): void {
|
||||
$guest_id = get_post_meta( $post->ID, self::META_PREFIX . 'guest_id', true );
|
||||
$guest_id = (int) get_post_meta( $post->ID, self::META_PREFIX . 'guest_id', true );
|
||||
$guest_name = get_post_meta( $post->ID, self::META_PREFIX . 'guest_name', true );
|
||||
$guest_email = get_post_meta( $post->ID, self::META_PREFIX . 'guest_email', true );
|
||||
$guest_phone = get_post_meta( $post->ID, self::META_PREFIX . 'guest_phone', true );
|
||||
@@ -314,7 +330,7 @@ final class Booking {
|
||||
$guest_phone = get_post_meta( $guest_id, '_bnb_guest_phone', true );
|
||||
} else {
|
||||
$linked_guest = null;
|
||||
$guest_id = '';
|
||||
$guest_id = 0;
|
||||
}
|
||||
}
|
||||
?>
|
||||
@@ -757,23 +773,26 @@ final class Booking {
|
||||
delete_post_meta( $post_id, self::META_PREFIX . 'guest_id' );
|
||||
}
|
||||
} else {
|
||||
delete_post_meta( $post_id, self::META_PREFIX . 'guest_id' );
|
||||
// No guest_id selected - get guest data from form fields.
|
||||
$guest_name = isset( $_POST['bnb_booking_guest_name'] ) ? sanitize_text_field( wp_unslash( $_POST['bnb_booking_guest_name'] ) ) : '';
|
||||
$guest_email = isset( $_POST['bnb_booking_guest_email'] ) ? sanitize_email( wp_unslash( $_POST['bnb_booking_guest_email'] ) ) : '';
|
||||
$guest_phone = isset( $_POST['bnb_booking_guest_phone'] ) ? sanitize_text_field( wp_unslash( $_POST['bnb_booking_guest_phone'] ) ) : '';
|
||||
|
||||
// Guest text fields (only save if no guest_id).
|
||||
$guest_fields = array( 'guest_name', 'guest_email', 'guest_phone', 'guest_notes' );
|
||||
foreach ( $guest_fields as $field ) {
|
||||
$key = 'bnb_booking_' . $field;
|
||||
if ( isset( $_POST[ $key ] ) ) {
|
||||
$value = wp_unslash( $_POST[ $key ] );
|
||||
if ( 'guest_email' === $field ) {
|
||||
$value = sanitize_email( $value );
|
||||
} elseif ( 'guest_notes' === $field ) {
|
||||
$value = sanitize_textarea_field( $value );
|
||||
} else {
|
||||
$value = sanitize_text_field( $value );
|
||||
}
|
||||
update_post_meta( $post_id, self::META_PREFIX . $field, $value );
|
||||
}
|
||||
// Try to find or create a Guest post.
|
||||
$linked_guest_id = self::find_or_create_guest( $guest_name, $guest_email, $guest_phone );
|
||||
|
||||
if ( $linked_guest_id ) {
|
||||
// Link to the guest and sync data.
|
||||
update_post_meta( $post_id, self::META_PREFIX . 'guest_id', $linked_guest_id );
|
||||
update_post_meta( $post_id, self::META_PREFIX . 'guest_name', Guest::get_full_name( $linked_guest_id ) );
|
||||
update_post_meta( $post_id, self::META_PREFIX . 'guest_email', get_post_meta( $linked_guest_id, '_bnb_guest_email', true ) );
|
||||
update_post_meta( $post_id, self::META_PREFIX . 'guest_phone', get_post_meta( $linked_guest_id, '_bnb_guest_phone', true ) );
|
||||
} else {
|
||||
// Fallback: save guest data directly to booking meta if guest creation failed.
|
||||
delete_post_meta( $post_id, self::META_PREFIX . 'guest_id' );
|
||||
update_post_meta( $post_id, self::META_PREFIX . 'guest_name', $guest_name );
|
||||
update_post_meta( $post_id, self::META_PREFIX . 'guest_email', $guest_email );
|
||||
update_post_meta( $post_id, self::META_PREFIX . 'guest_phone', $guest_phone );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -877,6 +896,130 @@ final class Booking {
|
||||
*/
|
||||
do_action( 'wp_bnb_booking_status_changed', $post_id, $old_status, $status );
|
||||
}
|
||||
|
||||
// Generate comprehensive title with guest name, room, and dates.
|
||||
self::generate_comprehensive_title( $post_id, $room_id, $check_in, $check_out );
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a comprehensive title for a booking.
|
||||
*
|
||||
* Format: "Guest Name (DD.MM - DD.MM.YYYY)"
|
||||
*
|
||||
* @param int $post_id Booking post ID.
|
||||
* @param int $room_id Room post ID (unused, kept for signature compatibility).
|
||||
* @param string $check_in Check-in date (Y-m-d).
|
||||
* @param string $check_out Check-out date (Y-m-d).
|
||||
* @return void
|
||||
*/
|
||||
private static function generate_comprehensive_title( int $post_id, int $room_id, string $check_in, string $check_out ): void {
|
||||
// Get guest name.
|
||||
$guest_name = get_post_meta( $post_id, self::META_PREFIX . 'guest_name', true );
|
||||
if ( empty( $guest_name ) ) {
|
||||
$guest_name = __( 'Unknown Guest', 'wp-bnb' );
|
||||
}
|
||||
|
||||
// Format dates.
|
||||
$date_part = '';
|
||||
if ( $check_in && $check_out ) {
|
||||
$check_in_date = \DateTime::createFromFormat( 'Y-m-d', $check_in );
|
||||
$check_out_date = \DateTime::createFromFormat( 'Y-m-d', $check_out );
|
||||
|
||||
if ( $check_in_date && $check_out_date ) {
|
||||
// Same year: "01.02 - 05.02.2026"
|
||||
// Different year: "28.12.2025 - 02.01.2026"
|
||||
if ( $check_in_date->format( 'Y' ) === $check_out_date->format( 'Y' ) ) {
|
||||
$date_part = sprintf(
|
||||
'%s - %s',
|
||||
$check_in_date->format( 'd.m' ),
|
||||
$check_out_date->format( 'd.m.Y' )
|
||||
);
|
||||
} else {
|
||||
$date_part = sprintf(
|
||||
'%s - %s',
|
||||
$check_in_date->format( 'd.m.Y' ),
|
||||
$check_out_date->format( 'd.m.Y' )
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Build title: "Guest Name (dates)".
|
||||
$title = $guest_name;
|
||||
if ( $date_part ) {
|
||||
$title .= sprintf( ' (%s)', $date_part );
|
||||
}
|
||||
|
||||
// Update the post title directly in the database to avoid infinite loop.
|
||||
global $wpdb;
|
||||
$wpdb->update(
|
||||
$wpdb->posts,
|
||||
array( 'post_title' => $title ),
|
||||
array( 'ID' => $post_id ),
|
||||
array( '%s' ),
|
||||
array( '%d' )
|
||||
);
|
||||
|
||||
// Clear post cache.
|
||||
clean_post_cache( $post_id );
|
||||
}
|
||||
|
||||
/**
|
||||
* Find an existing guest by email or create a new one.
|
||||
*
|
||||
* @param string $name Guest full name.
|
||||
* @param string $email Guest email.
|
||||
* @param string $phone Guest phone (optional).
|
||||
* @return int|null Guest post ID or null on failure.
|
||||
*/
|
||||
private static function find_or_create_guest( string $name, string $email, string $phone = '' ): ?int {
|
||||
// Need at least a name to create a guest.
|
||||
if ( empty( $name ) ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Try to find existing guest by email.
|
||||
if ( ! empty( $email ) ) {
|
||||
$existing_guest = Guest::get_by_email( $email );
|
||||
if ( $existing_guest ) {
|
||||
return $existing_guest->ID;
|
||||
}
|
||||
}
|
||||
|
||||
// Parse name into first/last name.
|
||||
$name_parts = explode( ' ', trim( $name ), 2 );
|
||||
$first_name = $name_parts[0] ?? '';
|
||||
$last_name = $name_parts[1] ?? '';
|
||||
|
||||
// Create new guest post.
|
||||
$guest_id = wp_insert_post(
|
||||
array(
|
||||
'post_type' => Guest::POST_TYPE,
|
||||
'post_status' => 'publish',
|
||||
'post_title' => $name,
|
||||
)
|
||||
);
|
||||
|
||||
if ( is_wp_error( $guest_id ) || ! $guest_id ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Save guest meta.
|
||||
update_post_meta( $guest_id, '_bnb_guest_first_name', $first_name );
|
||||
update_post_meta( $guest_id, '_bnb_guest_last_name', $last_name );
|
||||
|
||||
if ( ! empty( $email ) ) {
|
||||
update_post_meta( $guest_id, '_bnb_guest_email', $email );
|
||||
}
|
||||
|
||||
if ( ! empty( $phone ) ) {
|
||||
update_post_meta( $guest_id, '_bnb_guest_phone', $phone );
|
||||
}
|
||||
|
||||
// Set default status.
|
||||
update_post_meta( $guest_id, '_bnb_guest_status', 'active' );
|
||||
|
||||
return $guest_id;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -913,7 +1056,7 @@ final class Booking {
|
||||
public static function render_column( string $column, int $post_id ): void {
|
||||
switch ( $column ) {
|
||||
case 'room':
|
||||
$room_id = get_post_meta( $post_id, self::META_PREFIX . 'room_id', true );
|
||||
$room_id = (int) get_post_meta( $post_id, self::META_PREFIX . 'room_id', true );
|
||||
if ( $room_id ) {
|
||||
$room = get_post( $room_id );
|
||||
if ( $room ) {
|
||||
@@ -935,7 +1078,7 @@ final class Booking {
|
||||
break;
|
||||
|
||||
case 'guest':
|
||||
$guest_id = get_post_meta( $post_id, self::META_PREFIX . 'guest_id', true );
|
||||
$guest_id = (int) get_post_meta( $post_id, self::META_PREFIX . 'guest_id', true );
|
||||
$guest_name = get_post_meta( $post_id, self::META_PREFIX . 'guest_name', true );
|
||||
$guest_email = get_post_meta( $post_id, self::META_PREFIX . 'guest_email', true );
|
||||
if ( $guest_name ) {
|
||||
@@ -1096,6 +1239,13 @@ final class Booking {
|
||||
return;
|
||||
}
|
||||
|
||||
// Exclude auto-drafts from the list - they're not real bookings.
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Filter query only.
|
||||
$post_status = isset( $_GET['post_status'] ) ? sanitize_key( $_GET['post_status'] ) : '';
|
||||
if ( empty( $post_status ) || 'all' === $post_status ) {
|
||||
$query->set( 'post_status', array( 'publish', 'pending', 'draft', 'private' ) );
|
||||
}
|
||||
|
||||
$meta_query = array();
|
||||
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Filter query only.
|
||||
@@ -1142,31 +1292,11 @@ final class Booking {
|
||||
*/
|
||||
public static function change_title_placeholder( string $placeholder, \WP_Post $post ): string {
|
||||
if ( self::POST_TYPE === $post->post_type ) {
|
||||
return __( 'Booking reference (auto-generated)', 'wp-bnb' );
|
||||
return __( 'Title auto-generated from guest name and dates', 'wp-bnb' );
|
||||
}
|
||||
return $placeholder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Auto-generate booking reference as title.
|
||||
*
|
||||
* @param array $data Post data.
|
||||
* @param array $postarr Post array.
|
||||
* @return array
|
||||
*/
|
||||
public static function auto_generate_title( array $data, array $postarr ): array {
|
||||
if ( self::POST_TYPE !== $data['post_type'] ) {
|
||||
return $data;
|
||||
}
|
||||
|
||||
// Only generate if title is empty or matches auto-generated pattern.
|
||||
if ( empty( $data['post_title'] ) || preg_match( '/^BNB-\d{4}-\d{5}$/', $data['post_title'] ) ) {
|
||||
$data['post_title'] = self::generate_reference();
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Show conflict notice in admin.
|
||||
*
|
||||
|
||||
Reference in New Issue
Block a user