init_hooks(); } /** * Initialize hooks. * * @return void */ public static function init(): void { self::get_instance(); } /** * Initialize WordPress hooks. * * @return void */ private function init_hooks(): void { // Register personal data exporters. add_filter( 'wp_privacy_personal_data_exporters', array( $this, 'register_exporters' ) ); // Register personal data erasers. add_filter( 'wp_privacy_personal_data_erasers', array( $this, 'register_erasers' ) ); // Add privacy policy content suggestion. add_action( 'admin_init', array( $this, 'add_privacy_policy_content' ) ); } /** * Register personal data exporters. * * @param array $exporters Existing exporters. * @return array */ public function register_exporters( array $exporters ): array { $exporters['wp-bnb-guest'] = array( 'exporter_friendly_name' => __( 'WP BnB Guest Profile', 'wp-bnb' ), 'callback' => array( $this, 'export_guest_data' ), ); $exporters['wp-bnb-bookings'] = array( 'exporter_friendly_name' => __( 'WP BnB Booking History', 'wp-bnb' ), 'callback' => array( $this, 'export_booking_data' ), ); return $exporters; } /** * Register personal data erasers. * * @param array $erasers Existing erasers. * @return array */ public function register_erasers( array $erasers ): array { $erasers['wp-bnb-guest'] = array( 'eraser_friendly_name' => __( 'WP BnB Guest Profile', 'wp-bnb' ), 'callback' => array( $this, 'erase_guest_data' ), ); $erasers['wp-bnb-bookings'] = array( 'eraser_friendly_name' => __( 'WP BnB Booking History', 'wp-bnb' ), 'callback' => array( $this, 'erase_booking_data' ), ); return $erasers; } /** * Export guest profile data. * * @param string $email Email address to export data for. * @param int $page Page number for pagination. * @return array Export data array. */ public function export_guest_data( string $email, int $page = 1 ): array { $export_items = array(); // Find guest by email. $guest = Guest::get_by_email( $email ); if ( $guest ) { $data = array(); // Basic information. $first_name = get_post_meta( $guest->ID, '_bnb_guest_first_name', true ); $last_name = get_post_meta( $guest->ID, '_bnb_guest_last_name', true ); if ( $first_name ) { $data[] = array( 'name' => __( 'First Name', 'wp-bnb' ), 'value' => $first_name, ); } if ( $last_name ) { $data[] = array( 'name' => __( 'Last Name', 'wp-bnb' ), 'value' => $last_name, ); } $data[] = array( 'name' => __( 'Email', 'wp-bnb' ), 'value' => get_post_meta( $guest->ID, '_bnb_guest_email', true ), ); $phone = get_post_meta( $guest->ID, '_bnb_guest_phone', true ); if ( $phone ) { $data[] = array( 'name' => __( 'Phone', 'wp-bnb' ), 'value' => $phone, ); } // Address. $street = get_post_meta( $guest->ID, '_bnb_guest_street', true ); if ( $street ) { $data[] = array( 'name' => __( 'Street Address', 'wp-bnb' ), 'value' => $street, ); } $city = get_post_meta( $guest->ID, '_bnb_guest_city', true ); if ( $city ) { $data[] = array( 'name' => __( 'City', 'wp-bnb' ), 'value' => $city, ); } $postal_code = get_post_meta( $guest->ID, '_bnb_guest_postal_code', true ); if ( $postal_code ) { $data[] = array( 'name' => __( 'Postal Code', 'wp-bnb' ), 'value' => $postal_code, ); } $country = get_post_meta( $guest->ID, '_bnb_guest_country', true ); if ( $country ) { $data[] = array( 'name' => __( 'Country', 'wp-bnb' ), 'value' => $country, ); } // Personal details. $nationality = get_post_meta( $guest->ID, '_bnb_guest_nationality', true ); if ( $nationality ) { $data[] = array( 'name' => __( 'Nationality', 'wp-bnb' ), 'value' => $nationality, ); } $date_of_birth = get_post_meta( $guest->ID, '_bnb_guest_date_of_birth', true ); if ( $date_of_birth ) { $data[] = array( 'name' => __( 'Date of Birth', 'wp-bnb' ), 'value' => $date_of_birth, ); } // ID information (sensitive). $id_type = get_post_meta( $guest->ID, '_bnb_guest_id_type', true ); if ( $id_type ) { $data[] = array( 'name' => __( 'ID Type', 'wp-bnb' ), 'value' => $id_type, ); } $id_number = get_post_meta( $guest->ID, '_bnb_guest_id_number', true ); if ( $id_number ) { $data[] = array( 'name' => __( 'ID Number', 'wp-bnb' ), 'value' => $id_number, ); } $id_expiry = get_post_meta( $guest->ID, '_bnb_guest_id_expiry', true ); if ( $id_expiry ) { $data[] = array( 'name' => __( 'ID Expiry Date', 'wp-bnb' ), 'value' => $id_expiry, ); } // Consent information. $consent_data = get_post_meta( $guest->ID, '_bnb_guest_consent_data', true ); $data[] = array( 'name' => __( 'Data Processing Consent', 'wp-bnb' ), 'value' => $consent_data ? __( 'Yes', 'wp-bnb' ) : __( 'No', 'wp-bnb' ), ); $consent_marketing = get_post_meta( $guest->ID, '_bnb_guest_consent_marketing', true ); $data[] = array( 'name' => __( 'Marketing Consent', 'wp-bnb' ), 'value' => $consent_marketing ? __( 'Yes', 'wp-bnb' ) : __( 'No', 'wp-bnb' ), ); $consent_date = get_post_meta( $guest->ID, '_bnb_guest_consent_date', true ); if ( $consent_date ) { $data[] = array( 'name' => __( 'Consent Date', 'wp-bnb' ), 'value' => $consent_date, ); } // Notes and preferences. $preferences = get_post_meta( $guest->ID, '_bnb_guest_preferences', true ); if ( $preferences ) { $data[] = array( 'name' => __( 'Guest Preferences', 'wp-bnb' ), 'value' => $preferences, ); } if ( ! empty( $data ) ) { $export_items[] = array( 'group_id' => 'wp-bnb-guest', 'group_label' => __( 'Guest Profile', 'wp-bnb' ), 'group_description' => __( 'Your guest profile information stored by WP BnB.', 'wp-bnb' ), 'item_id' => 'guest-' . $guest->ID, 'data' => $data, ); } } return array( 'data' => $export_items, 'done' => true, ); } /** * Export booking history data. * * @param string $email Email address to export data for. * @param int $page Page number for pagination. * @return array Export data array. */ public function export_booking_data( string $email, int $page = 1 ): array { $export_items = array(); $per_page = 20; $offset = ( $page - 1 ) * $per_page; // Find bookings by email (both direct and through guest_id). $bookings = get_posts( array( 'post_type' => Booking::POST_TYPE, 'post_status' => 'any', 'posts_per_page' => $per_page, 'offset' => $offset, 'meta_query' => array( 'relation' => 'OR', array( 'key' => '_bnb_booking_guest_email', 'value' => $email, ), ), ) ); // Also check via guest_id. $guest = Guest::get_by_email( $email ); if ( $guest ) { $bookings_by_id = get_posts( array( 'post_type' => Booking::POST_TYPE, 'post_status' => 'any', 'posts_per_page' => $per_page, 'meta_query' => array( array( 'key' => '_bnb_booking_guest_id', 'value' => $guest->ID, ), ), ) ); // Merge and dedupe. $existing_ids = wp_list_pluck( $bookings, 'ID' ); foreach ( $bookings_by_id as $booking ) { if ( ! in_array( $booking->ID, $existing_ids, true ) ) { $bookings[] = $booking; } } } foreach ( $bookings as $booking ) { $data = array(); $reference = get_post_meta( $booking->ID, '_bnb_booking_reference', true ); if ( ! $reference ) { $reference = 'BNB-' . $booking->ID; } $data[] = array( 'name' => __( 'Booking Reference', 'wp-bnb' ), 'value' => $reference, ); $room_id = get_post_meta( $booking->ID, '_bnb_booking_room_id', true ); if ( $room_id ) { $room = get_post( $room_id ); if ( $room ) { $data[] = array( 'name' => __( 'Room', 'wp-bnb' ), 'value' => $room->post_title, ); } } $check_in = get_post_meta( $booking->ID, '_bnb_booking_check_in', true ); if ( $check_in ) { $data[] = array( 'name' => __( 'Check-in Date', 'wp-bnb' ), 'value' => $check_in, ); } $check_out = get_post_meta( $booking->ID, '_bnb_booking_check_out', true ); if ( $check_out ) { $data[] = array( 'name' => __( 'Check-out Date', 'wp-bnb' ), 'value' => $check_out, ); } $status = get_post_meta( $booking->ID, '_bnb_booking_status', true ); if ( $status ) { $statuses = Booking::get_booking_statuses(); $data[] = array( 'name' => __( 'Status', 'wp-bnb' ), 'value' => $statuses[ $status ] ?? $status, ); } $adults = get_post_meta( $booking->ID, '_bnb_booking_adults', true ); if ( $adults ) { $data[] = array( 'name' => __( 'Adults', 'wp-bnb' ), 'value' => $adults, ); } $children = get_post_meta( $booking->ID, '_bnb_booking_children', true ); if ( $children ) { $data[] = array( 'name' => __( 'Children', 'wp-bnb' ), 'value' => $children, ); } $price = get_post_meta( $booking->ID, '_bnb_booking_calculated_price', true ); if ( $price ) { $currency = get_option( 'wp_bnb_currency', 'CHF' ); $data[] = array( 'name' => __( 'Total Price', 'wp-bnb' ), 'value' => number_format( (float) $price, 2 ) . ' ' . $currency, ); } $guest_notes = get_post_meta( $booking->ID, '_bnb_booking_guest_notes', true ); if ( $guest_notes ) { $data[] = array( 'name' => __( 'Guest Notes', 'wp-bnb' ), 'value' => $guest_notes, ); } if ( ! empty( $data ) ) { $export_items[] = array( 'group_id' => 'wp-bnb-bookings', 'group_label' => __( 'Booking History', 'wp-bnb' ), 'group_description' => __( 'Your booking history with WP BnB.', 'wp-bnb' ), 'item_id' => 'booking-' . $booking->ID, 'data' => $data, ); } } // Check if there are more bookings. $total_bookings = $this->count_bookings_by_email( $email ); $done = ( $offset + $per_page ) >= $total_bookings; return array( 'data' => $export_items, 'done' => $done, ); } /** * Erase guest profile data. * * @param string $email Email address to erase data for. * @param int $page Page number for pagination. * @return array Erasure result array. */ public function erase_guest_data( string $email, int $page = 1 ): array { $items_removed = 0; $items_retained = 0; $messages = array(); // Find guest by email. $guest = Guest::get_by_email( $email ); if ( $guest ) { // Check if guest has active bookings. $active_bookings = get_posts( array( 'post_type' => Booking::POST_TYPE, 'post_status' => 'publish', 'posts_per_page' => 1, 'meta_query' => array( 'relation' => 'AND', array( 'relation' => 'OR', array( 'key' => '_bnb_booking_guest_id', 'value' => $guest->ID, ), array( 'key' => '_bnb_booking_guest_email', 'value' => $email, ), ), array( 'key' => '_bnb_booking_status', 'value' => array( 'pending', 'confirmed', 'checked_in' ), 'compare' => 'IN', ), ), ) ); if ( ! empty( $active_bookings ) ) { // Cannot delete - has active bookings. $messages[] = __( 'Guest profile retained due to active bookings.', 'wp-bnb' ); $items_retained = 1; } else { // Anonymize the guest profile instead of deleting. $this->anonymize_guest( $guest->ID ); $items_removed = 1; $messages[] = __( 'Guest profile anonymized.', 'wp-bnb' ); } } return array( 'items_removed' => $items_removed, 'items_retained' => $items_retained, 'messages' => $messages, 'done' => true, ); } /** * Erase booking data. * * @param string $email Email address to erase data for. * @param int $page Page number for pagination. * @return array Erasure result array. */ public function erase_booking_data( string $email, int $page = 1 ): array { $items_removed = 0; $items_retained = 0; $messages = array(); $per_page = 20; // Find completed bookings (can be anonymized). $bookings = get_posts( array( 'post_type' => Booking::POST_TYPE, 'post_status' => 'any', 'posts_per_page' => $per_page, 'meta_query' => array( 'relation' => 'AND', array( 'key' => '_bnb_booking_guest_email', 'value' => $email, ), array( 'key' => '_bnb_booking_status', 'value' => array( 'checked_out', 'cancelled' ), 'compare' => 'IN', ), ), ) ); // Also find by guest_id. $guest = Guest::get_by_email( $email ); if ( $guest ) { $more_bookings = get_posts( array( 'post_type' => Booking::POST_TYPE, 'post_status' => 'any', 'posts_per_page' => $per_page, 'meta_query' => array( 'relation' => 'AND', array( 'key' => '_bnb_booking_guest_id', 'value' => $guest->ID, ), array( 'key' => '_bnb_booking_status', 'value' => array( 'checked_out', 'cancelled' ), 'compare' => 'IN', ), ), ) ); $existing_ids = wp_list_pluck( $bookings, 'ID' ); foreach ( $more_bookings as $booking ) { if ( ! in_array( $booking->ID, $existing_ids, true ) ) { $bookings[] = $booking; } } } foreach ( $bookings as $booking ) { $this->anonymize_booking( $booking->ID ); ++$items_removed; } // Check for active bookings that can't be erased. $active_bookings = get_posts( array( 'post_type' => Booking::POST_TYPE, 'post_status' => 'publish', 'posts_per_page' => -1, 'fields' => 'ids', 'meta_query' => array( 'relation' => 'AND', array( 'key' => '_bnb_booking_guest_email', 'value' => $email, ), array( 'key' => '_bnb_booking_status', 'value' => array( 'pending', 'confirmed', 'checked_in' ), 'compare' => 'IN', ), ), ) ); $items_retained = count( $active_bookings ); if ( $items_retained > 0 ) { $messages[] = sprintf( /* translators: %d: Number of bookings */ _n( '%d booking retained due to active status.', '%d bookings retained due to active status.', $items_retained, 'wp-bnb' ), $items_retained ); } if ( $items_removed > 0 ) { $messages[] = sprintf( /* translators: %d: Number of bookings */ _n( '%d booking anonymized.', '%d bookings anonymized.', $items_removed, 'wp-bnb' ), $items_removed ); } return array( 'items_removed' => $items_removed, 'items_retained' => $items_retained, 'messages' => $messages, 'done' => true, ); } /** * Anonymize a guest record. * * @param int $guest_id Guest post ID. * @return bool True on success. */ public function anonymize_guest( int $guest_id ): bool { $anonymized = __( '[Deleted]', 'wp-bnb' ); // Update post title. wp_update_post( array( 'ID' => $guest_id, 'post_title' => $anonymized, ) ); // Anonymize personal data. update_post_meta( $guest_id, '_bnb_guest_first_name', $anonymized ); update_post_meta( $guest_id, '_bnb_guest_last_name', '' ); update_post_meta( $guest_id, '_bnb_guest_email', 'deleted-' . $guest_id . '@anonymized.local' ); update_post_meta( $guest_id, '_bnb_guest_phone', '' ); update_post_meta( $guest_id, '_bnb_guest_street', '' ); update_post_meta( $guest_id, '_bnb_guest_city', '' ); update_post_meta( $guest_id, '_bnb_guest_postal_code', '' ); update_post_meta( $guest_id, '_bnb_guest_country', '' ); update_post_meta( $guest_id, '_bnb_guest_nationality', '' ); update_post_meta( $guest_id, '_bnb_guest_date_of_birth', '' ); update_post_meta( $guest_id, '_bnb_guest_id_type', '' ); update_post_meta( $guest_id, '_bnb_guest_id_number', '' ); update_post_meta( $guest_id, '_bnb_guest_id_expiry', '' ); update_post_meta( $guest_id, '_bnb_guest_preferences', '' ); update_post_meta( $guest_id, '_bnb_guest_notes', '' ); update_post_meta( $guest_id, '_bnb_guest_status', 'inactive' ); return true; } /** * Anonymize a booking record. * * @param int $booking_id Booking post ID. * @return bool True on success. */ public function anonymize_booking( int $booking_id ): bool { $anonymized = __( '[Deleted]', 'wp-bnb' ); // Remove guest reference. delete_post_meta( $booking_id, '_bnb_booking_guest_id' ); // Anonymize guest data stored in booking. update_post_meta( $booking_id, '_bnb_booking_guest_name', $anonymized ); update_post_meta( $booking_id, '_bnb_booking_guest_email', '' ); update_post_meta( $booking_id, '_bnb_booking_guest_phone', '' ); update_post_meta( $booking_id, '_bnb_booking_guest_notes', '' ); return true; } /** * Count bookings by email. * * @param string $email Email address. * @return int Count of bookings. */ private function count_bookings_by_email( string $email ): int { $count = 0; // Direct email match. $direct = get_posts( array( 'post_type' => Booking::POST_TYPE, 'post_status' => 'any', 'posts_per_page' => -1, 'fields' => 'ids', 'meta_query' => array( array( 'key' => '_bnb_booking_guest_email', 'value' => $email, ), ), ) ); $count += count( $direct ); // Guest ID match. $guest = Guest::get_by_email( $email ); if ( $guest ) { $by_guest_id = get_posts( array( 'post_type' => Booking::POST_TYPE, 'post_status' => 'any', 'posts_per_page' => -1, 'fields' => 'ids', 'post__not_in' => $direct, 'meta_query' => array( array( 'key' => '_bnb_booking_guest_id', 'value' => $guest->ID, ), ), ) ); $count += count( $by_guest_id ); } return $count; } /** * Add privacy policy content suggestion. * * @return void */ public function add_privacy_policy_content(): void { if ( ! function_exists( 'wp_add_privacy_policy_content' ) ) { return; } $content = sprintf( '

%s

%s

%s

%s

%s

%s

%s

%s

%s

%s

', __( 'Accommodation Booking', 'wp-bnb' ), __( 'When you make a booking with us, we collect and process the following personal data to fulfill your reservation and comply with legal requirements.', 'wp-bnb' ), __( 'What personal data we collect', 'wp-bnb' ), __( 'We collect the following information when you make a booking:', 'wp-bnb' ), __( 'Name and contact information (email, phone)', 'wp-bnb' ), __( 'Address for billing and guest registration', 'wp-bnb' ), __( 'Identity document information (as required by local regulations)', 'wp-bnb' ), __( 'Booking details (dates, room preferences, special requests)', 'wp-bnb' ), __( 'Payment information (processed securely by payment providers)', 'wp-bnb' ), __( 'Why we collect this data', 'wp-bnb' ), __( 'We use your personal data for the following purposes:', 'wp-bnb' ), __( 'Processing and managing your booking', 'wp-bnb' ), __( 'Communicating with you about your reservation', 'wp-bnb' ), __( 'Complying with legal guest registration requirements', 'wp-bnb' ), __( 'How long we retain your data', 'wp-bnb' ), __( 'We retain your booking data for the period required by law for guest registration and accounting purposes, typically 10 years. After this period, your data will be anonymized or deleted.', 'wp-bnb' ), __( 'Your rights', 'wp-bnb' ), __( 'You have the right to access, correct, or request deletion of your personal data. To exercise these rights, please contact us using the information provided on this website. Note that some data may need to be retained for legal compliance purposes.', 'wp-bnb' ) ); wp_add_privacy_policy_content( 'WP BnB', wp_kses_post( $content ) ); } }