post_status ) { return; } // Check if auto-sync is enabled. if ( ! Manager::is_auto_sync_enabled() ) { return; } // Sync the product. self::sync_room_to_product( $post_id ); } /** * Handle room deletion - delete linked product. * * @param int $post_id Post ID. * @return void */ public static function on_room_delete( int $post_id ): void { $post = get_post( $post_id ); if ( ! $post || Room::POST_TYPE !== $post->post_type ) { return; } self::delete_product_for_room( $post_id ); } /** * Sync a room to a WooCommerce product. * * Creates a new product if one doesn't exist, or updates existing one. * * @param int $room_id Room post ID. * @return int|null Product ID or null on failure. */ public static function sync_room_to_product( int $room_id ): ?int { $room = get_post( $room_id ); if ( ! $room || Room::POST_TYPE !== $room->post_type ) { return null; } // Check for existing product. $product_id = self::get_product_for_room( $room_id ); if ( $product_id ) { // Update existing product. return self::update_product( $product_id, $room ); } // Create new product. return self::create_product_for_room( $room ); } /** * Create a WooCommerce product for a room. * * @param \WP_Post $room Room post object. * @return int|null Product ID or null on failure. */ public static function create_product_for_room( \WP_Post $room ): ?int { /** * Fires before creating a WC product for a room. * * @param int $room_id Room post ID. */ do_action( 'wp_bnb_wc_before_product_sync', $room->ID ); // Create a simple virtual product. $product = new \WC_Product_Simple(); // Configure the product. self::configure_product( $product, $room ); // Save the product. $product_id = $product->save(); if ( ! $product_id ) { return null; } // Store bidirectional links. update_post_meta( $room->ID, Manager::ROOM_PRODUCT_META, $product_id ); update_post_meta( $product_id, Manager::PRODUCT_ROOM_META, $room->ID ); /** * Fires after creating a WC product for a room. * * @param int $room_id Room post ID. * @param int $product_id WC product ID. */ do_action( 'wp_bnb_wc_after_product_sync', $room->ID, $product_id ); return $product_id; } /** * Update an existing WooCommerce product. * * @param int $product_id Product ID. * @param \WP_Post $room Room post object. * @return int|null Product ID or null on failure. */ private static function update_product( int $product_id, \WP_Post $room ): ?int { $product = wc_get_product( $product_id ); if ( ! $product ) { // Product was deleted, create a new one. return self::create_product_for_room( $room ); } /** * Fires before updating a WC product for a room. * * @param int $room_id Room post ID. */ do_action( 'wp_bnb_wc_before_product_sync', $room->ID ); // Configure the product. self::configure_product( $product, $room ); // Save the product. $product->save(); /** * Fires after updating a WC product for a room. * * @param int $room_id Room post ID. * @param int $product_id WC product ID. */ do_action( 'wp_bnb_wc_after_product_sync', $room->ID, $product_id ); return $product_id; } /** * Configure a WooCommerce product from room data. * * @param \WC_Product $product Product object. * @param \WP_Post $room Room post object. * @return void */ private static function configure_product( \WC_Product $product, \WP_Post $room ): void { // Get room data. $building = Room::get_building( $room->ID ); $building_name = $building ? $building->post_title : ''; $pricing = Calculator::getRoomPricing( $room->ID ); // Basic info. $product->set_name( $room->post_title ); $product->set_slug( 'bnb-room-' . $room->ID ); $product->set_status( 'publish' ); // SKU. $product->set_sku( 'bnb-room-' . $room->ID ); // Virtual product (no shipping). $product->set_virtual( true ); // Description. $description = $room->post_content; if ( $building_name ) { $description = sprintf( /* translators: %s: Building name */ __( 'Room at %s', 'wp-bnb' ), $building_name ) . "\n\n" . $description; } $product->set_description( $description ); $product->set_short_description( $room->post_excerpt ?: wp_trim_words( $room->post_content, 30 ) ); // Price (use short-term/nightly rate as base). $base_price = $pricing[ PricingTier::SHORT_TERM->value ]['price'] ?? 0; if ( $base_price > 0 ) { $product->set_regular_price( (string) $base_price ); } // Featured image. $thumbnail_id = get_post_thumbnail_id( $room->ID ); if ( $thumbnail_id ) { $product->set_image_id( $thumbnail_id ); } // Gallery images. $gallery_ids = get_post_meta( $room->ID, '_bnb_room_gallery', true ); if ( $gallery_ids ) { $ids = array_filter( explode( ',', $gallery_ids ), 'is_numeric' ); $product->set_gallery_image_ids( array_map( 'absint', $ids ) ); } // Stock management (disabled - availability handled by booking system). $product->set_manage_stock( false ); $product->set_stock_status( 'instock' ); // Catalog visibility - visible. $product->set_catalog_visibility( 'visible' ); // Product category. $category_id = Manager::get_product_category(); if ( $category_id ) { $product->set_category_ids( array( $category_id ) ); } // Store room metadata. $capacity = get_post_meta( $room->ID, '_bnb_room_capacity', true ); $beds = get_post_meta( $room->ID, '_bnb_room_beds', true ); $product->update_meta_data( '_bnb_room_capacity', $capacity ); $product->update_meta_data( '_bnb_room_beds', $beds ); $product->update_meta_data( '_bnb_building_id', $building ? $building->ID : 0 ); $product->update_meta_data( '_bnb_building_name', $building_name ); /** * Filter the product data before save. * * @param array $data Product data array. * @param int $room_id Room post ID. * @param \WP_Post $room Room post object. */ $data = apply_filters( 'wp_bnb_wc_product_data', array( 'name' => $product->get_name(), 'price' => $product->get_regular_price(), 'description' => $product->get_description(), ), $room->ID, $room ); // Apply filtered data. if ( isset( $data['name'] ) ) { $product->set_name( $data['name'] ); } if ( isset( $data['price'] ) ) { $product->set_regular_price( (string) $data['price'] ); } if ( isset( $data['description'] ) ) { $product->set_description( $data['description'] ); } } /** * Delete the WooCommerce product for a room. * * @param int $room_id Room post ID. * @return bool True if deleted, false otherwise. */ public static function delete_product_for_room( int $room_id ): bool { $product_id = self::get_product_for_room( $room_id ); if ( ! $product_id ) { return false; } $product = wc_get_product( $product_id ); if ( ! $product ) { return false; } // Delete the product (force delete, not trash). $product->delete( true ); // Clean up room meta. delete_post_meta( $room_id, Manager::ROOM_PRODUCT_META ); return true; } /** * Get the WooCommerce product ID for a room. * * @param int $room_id Room post ID. * @return int|null Product ID or null. */ public static function get_product_for_room( int $room_id ): ?int { $product_id = get_post_meta( $room_id, Manager::ROOM_PRODUCT_META, true ); if ( ! $product_id ) { return null; } // Verify product still exists. $product = wc_get_product( $product_id ); if ( ! $product ) { // Clean up stale meta. delete_post_meta( $room_id, Manager::ROOM_PRODUCT_META ); return null; } return absint( $product_id ); } /** * Get the room ID for a WooCommerce product. * * @param int $product_id Product ID. * @return int|null Room ID or null. */ public static function get_room_for_product( int $product_id ): ?int { $room_id = get_post_meta( $product_id, Manager::PRODUCT_ROOM_META, true ); if ( ! $room_id ) { return null; } // Verify room still exists. $room = get_post( $room_id ); if ( ! $room || Room::POST_TYPE !== $room->post_type ) { // Clean up stale meta. delete_post_meta( $product_id, Manager::PRODUCT_ROOM_META ); return null; } return absint( $room_id ); } /** * Sync all published rooms to WooCommerce products. * * @return array{created: int, updated: int, errors: array} */ public static function sync_all_rooms(): array { $result = array( 'created' => 0, 'updated' => 0, 'errors' => array(), ); $rooms = get_posts( array( 'post_type' => Room::POST_TYPE, 'post_status' => 'publish', 'posts_per_page' => -1, 'fields' => 'ids', ) ); foreach ( $rooms as $room_id ) { $existing_product = self::get_product_for_room( $room_id ); $product_id = self::sync_room_to_product( $room_id ); if ( $product_id ) { if ( $existing_product ) { ++$result['updated']; } else { ++$result['created']; } } else { $room = get_post( $room_id ); $result['errors'][] = sprintf( /* translators: %s: Room title */ __( 'Failed to sync room: %s', 'wp-bnb' ), $room ? $room->post_title : "#{$room_id}" ); } } return $result; } /** * AJAX handler for syncing all rooms. * * @return void */ public static function ajax_sync_all_rooms(): void { check_ajax_referer( 'wp_bnb_admin_nonce', 'nonce' ); if ( ! current_user_can( 'manage_options' ) ) { wp_send_json_error( array( 'message' => __( 'Permission denied.', 'wp-bnb' ) ) ); } $result = self::sync_all_rooms(); wp_send_json_success( array( 'message' => sprintf( /* translators: 1: Created count, 2: Updated count */ __( 'Sync complete. Created: %1$d, Updated: %2$d', 'wp-bnb' ), $result['created'], $result['updated'] ), 'created' => $result['created'], 'updated' => $result['updated'], 'errors' => $result['errors'], ) ); } /** * Add linked room info to WooCommerce product edit screen. * * @return void */ public static function add_product_room_info(): void { global $post; if ( ! $post ) { return; } $room_id = self::get_room_for_product( $post->ID ); if ( ! $room_id ) { return; } $room = get_post( $room_id ); if ( ! $room ) { return; } ?>

post_title ); ?>

get_meta( Manager::PRODUCT_ROOM_META, true ); return ! empty( $room_id ); } }