room_id = $room_id; if ( is_string( $check_in ) ) { $check_in = new \DateTimeImmutable( $check_in ); } elseif ( $check_in instanceof \DateTime ) { $check_in = \DateTimeImmutable::createFromMutable( $check_in ); } if ( is_string( $check_out ) ) { $check_out = new \DateTimeImmutable( $check_out ); } elseif ( $check_out instanceof \DateTime ) { $check_out = \DateTimeImmutable::createFromMutable( $check_out ); } $this->check_in = $check_in; $this->check_out = $check_out; } /** * Get the number of nights. * * @return int */ public function getNights(): int { $interval = $this->check_in->diff( $this->check_out ); return max( 1, $interval->days ); } /** * Get the pricing tier for this stay. * * @return PricingTier */ public function getTier(): PricingTier { return PricingTier::fromNights( $this->getNights() ); } /** * Get the base price for a room. * * @param PricingTier $tier Pricing tier. * @return float */ public function getBasePrice( PricingTier $tier ): float { $meta_key = self::META_PREFIX . $tier->value; $price = get_post_meta( $this->room_id, $meta_key, true ); return $price ? (float) $price : 0.0; } /** * Get the weekend surcharge for a room. * * @return float Surcharge as absolute amount. */ public function getWeekendSurcharge(): float { $surcharge = get_post_meta( $this->room_id, self::META_PREFIX . 'weekend_surcharge', true ); return $surcharge ? (float) $surcharge : 0.0; } /** * Check if weekend surcharge is enabled for this room. * * @return bool */ public function hasWeekendSurcharge(): bool { return $this->getWeekendSurcharge() > 0; } /** * Check if a date is a weekend day. * * @param \DateTimeInterface $date Date to check. * @return bool */ public static function isWeekend( \DateTimeInterface $date ): bool { $day_of_week = (int) $date->format( 'N' ); // 1 = Monday, 7 = Sunday. $weekend_days = array_map( 'intval', explode( ',', (string) get_option( 'wp_bnb_weekend_days', '5,6' ) ) // Default: Friday, Saturday. ); return in_array( $day_of_week, $weekend_days, true ); } /** * Calculate the total price. * * @return float */ public function calculate(): float { $this->breakdown = array(); $nights = $this->getNights(); $tier = $this->getTier(); $base_price = $this->getBasePrice( $tier ); $weekend_surcharge = $this->getWeekendSurcharge(); // If no base price is set, return 0. if ( $base_price <= 0 ) { return 0.0; } $total = 0.0; // Calculate based on tier. switch ( $tier ) { case PricingTier::LONG_TERM: // Monthly pricing. $months = ceil( $nights / 30 ); $total = $base_price * $months; $this->breakdown['months'] = $months; $this->breakdown['monthly_rate'] = $base_price; break; case PricingTier::MID_TERM: // Weekly pricing. $weeks = ceil( $nights / 7 ); $total = $base_price * $weeks; $this->breakdown['weeks'] = $weeks; $this->breakdown['weekly_rate'] = $base_price; break; case PricingTier::SHORT_TERM: default: // Nightly pricing with seasonal adjustments and weekend surcharges. $current = $this->check_in; $breakdown_nights = array(); for ( $i = 0; $i < $nights; $i++ ) { $night_price = $base_price; $modifiers = array(); // Apply seasonal pricing. $season = Season::forDate( $current ); if ( $season ) { $night_price *= $season->modifier; $modifiers['season'] = array( 'name' => $season->name, 'modifier' => $season->modifier, ); } // Apply weekend surcharge. if ( $weekend_surcharge > 0 && self::isWeekend( $current ) ) { $night_price += $weekend_surcharge; $modifiers['weekend'] = $weekend_surcharge; } $breakdown_nights[] = array( 'date' => $current->format( 'Y-m-d' ), 'base' => $base_price, 'final' => $night_price, 'modifiers' => $modifiers, ); $total += $night_price; $current = $current->modify( '+1 day' ); } $this->breakdown['nights'] = $breakdown_nights; $this->breakdown['nightly_rate'] = $base_price; break; } $this->breakdown['tier'] = $tier->value; $this->breakdown['total'] = $total; /** * Filter the calculated price. * * @param float $total Calculated total price. * @param int $room_id Room post ID. * @param Calculator $calculator Calculator instance. */ return (float) apply_filters( 'wp_bnb_calculate_price', $total, $this->room_id, $this ); } /** * Get the price breakdown. * * Must be called after calculate(). * * @return array */ public function getBreakdown(): array { return $this->breakdown; } /** * Format a price with currency. * * @param float $amount Amount to format. * @param string $currency Currency code. Default from settings. * @return string */ public static function formatPrice( float $amount, string $currency = '' ): string { if ( empty( $currency ) ) { $currency = get_option( 'wp_bnb_currency', 'CHF' ); } $symbols = array( 'CHF' => 'CHF', 'EUR' => "\u{20AC}", 'USD' => '$', 'GBP' => "\u{00A3}", ); $symbol = $symbols[ $currency ] ?? $currency; $formatted = number_format( $amount, 2, '.', "'" ); // CHF uses suffix, others use prefix. if ( 'CHF' === $currency ) { return $formatted . ' ' . $symbol; } return $symbol . ' ' . $formatted; } /** * Get room pricing summary. * * @param int $room_id Room post ID. * @return array */ public static function getRoomPricing( int $room_id ): array { $pricing = array(); foreach ( PricingTier::cases() as $tier ) { $meta_key = self::META_PREFIX . $tier->value; $price = get_post_meta( $room_id, $meta_key, true ); $pricing[ $tier->value ] = array( 'label' => $tier->label(), 'unit' => $tier->unit(), 'price' => $price ? (float) $price : null, ); } $pricing['weekend_surcharge'] = array( 'label' => __( 'Weekend Surcharge', 'wp-bnb' ), 'price' => (float) get_post_meta( $room_id, self::META_PREFIX . 'weekend_surcharge', true ), ); return $pricing; } /** * Save room pricing. * * @param int $room_id Room post ID. * @param array $pricing Pricing data with tier keys. * @return void */ public static function saveRoomPricing( int $room_id, array $pricing ): void { foreach ( PricingTier::cases() as $tier ) { if ( isset( $pricing[ $tier->value ] ) ) { update_post_meta( $room_id, self::META_PREFIX . $tier->value, floatval( $pricing[ $tier->value ] ) ); } } if ( isset( $pricing['weekend_surcharge'] ) ) { update_post_meta( $room_id, self::META_PREFIX . 'weekend_surcharge', floatval( $pricing['weekend_surcharge'] ) ); } } /** * Get the check-in date. * * @return \DateTimeImmutable */ public function getCheckIn(): \DateTimeImmutable { return $this->check_in; } /** * Get the check-out date. * * @return \DateTimeImmutable */ public function getCheckOut(): \DateTimeImmutable { return $this->check_out; } /** * Get the room ID. * * @return int */ public function getRoomId(): int { return $this->room_id; } }