id = $data['id'] ?? wp_generate_uuid4(); $this->name = $data['name'] ?? ''; $this->start_date = $data['start_date'] ?? ''; $this->end_date = $data['end_date'] ?? ''; $this->modifier = isset( $data['modifier'] ) ? (float) $data['modifier'] : 1.0; $this->priority = isset( $data['priority'] ) ? (int) $data['priority'] : 0; $this->active = isset( $data['active'] ) ? (bool) $data['active'] : true; } /** * Convert to array. * * @return array */ public function toArray(): array { return array( 'id' => $this->id, 'name' => $this->name, 'start_date' => $this->start_date, 'end_date' => $this->end_date, 'modifier' => $this->modifier, 'priority' => $this->priority, 'active' => $this->active, ); } /** * Check if a date falls within this season. * * @param \DateTimeInterface $date Date to check. * @param int|null $year Year context (for spanning seasons like winter). * @return bool */ public function containsDate( \DateTimeInterface $date, ?int $year = null ): bool { if ( ! $this->active ) { return false; } $year = $year ?? (int) $date->format( 'Y' ); // Parse start and end as month-day. $start_parts = explode( '-', $this->start_date ); $end_parts = explode( '-', $this->end_date ); if ( count( $start_parts ) !== 2 || count( $end_parts ) !== 2 ) { return false; } $start_month = (int) $start_parts[0]; $start_day = (int) $start_parts[1]; $end_month = (int) $end_parts[0]; $end_day = (int) $end_parts[1]; $check_month = (int) $date->format( 'm' ); $check_day = (int) $date->format( 'd' ); // Create comparable values (month * 100 + day). $check_value = $check_month * 100 + $check_day; $start_value = $start_month * 100 + $start_day; $end_value = $end_month * 100 + $end_day; // Handle year-spanning seasons (e.g., December to February). if ( $start_value > $end_value ) { // Season spans year boundary. return $check_value >= $start_value || $check_value <= $end_value; } // Normal season within same year. return $check_value >= $start_value && $check_value <= $end_value; } /** * Get the display label for the modifier. * * @return string */ public function getModifierLabel(): string { if ( $this->modifier === 1.0 ) { return __( 'Normal', 'wp-bnb' ); } $percentage = ( $this->modifier - 1 ) * 100; if ( $percentage > 0 ) { /* translators: %s: percentage increase */ return sprintf( __( '+%s%%', 'wp-bnb' ), number_format( $percentage, 0 ) ); } /* translators: %s: percentage decrease */ return sprintf( __( '%s%%', 'wp-bnb' ), number_format( $percentage, 0 ) ); } /** * Get all seasons. * * @return array */ public static function all(): array { $data = get_option( self::OPTION_NAME, array() ); $seasons = array(); foreach ( $data as $season_data ) { $seasons[] = new self( $season_data ); } // Sort by priority (descending). usort( $seasons, fn( Season $a, Season $b ) => $b->priority <=> $a->priority ); return $seasons; } /** * Get all active seasons. * * @return array */ public static function allActive(): array { return array_filter( self::all(), fn( Season $s ) => $s->active ); } /** * Find a season by ID. * * @param string $id Season ID. * @return self|null */ public static function find( string $id ): ?self { foreach ( self::all() as $season ) { if ( $season->id === $id ) { return $season; } } return null; } /** * Get the applicable season for a date. * * Returns the highest priority active season that contains the date. * * @param \DateTimeInterface $date Date to check. * @return self|null */ public static function forDate( \DateTimeInterface $date ): ?self { $seasons = self::allActive(); foreach ( $seasons as $season ) { if ( $season->containsDate( $date ) ) { return $season; } } return null; } /** * Save a season. * * @param self $season Season to save. * @return bool */ public static function save( self $season ): bool { $data = get_option( self::OPTION_NAME, array() ); $exists = false; foreach ( $data as $index => $existing ) { if ( $existing['id'] === $season->id ) { $data[ $index ] = $season->toArray(); $exists = true; break; } } if ( ! $exists ) { $data[] = $season->toArray(); } return update_option( self::OPTION_NAME, $data ); } /** * Delete a season. * * @param string $id Season ID to delete. * @return bool */ public static function delete( string $id ): bool { $data = get_option( self::OPTION_NAME, array() ); $data = array_filter( $data, fn( $s ) => $s['id'] !== $id ); return update_option( self::OPTION_NAME, array_values( $data ) ); } /** * Create default seasons. * * @return void */ public static function createDefaults(): void { $existing = get_option( self::OPTION_NAME, array() ); if ( ! empty( $existing ) ) { return; } $defaults = array( new self( array( 'name' => __( 'High Season', 'wp-bnb' ), 'start_date' => '06-15', 'end_date' => '09-15', 'modifier' => 1.25, 'priority' => 10, 'active' => true, ) ), new self( array( 'name' => __( 'Winter Holidays', 'wp-bnb' ), 'start_date' => '12-20', 'end_date' => '01-06', 'modifier' => 1.30, 'priority' => 20, 'active' => true, ) ), new self( array( 'name' => __( 'Low Season', 'wp-bnb' ), 'start_date' => '11-01', 'end_date' => '03-31', 'modifier' => 0.85, 'priority' => 5, 'active' => true, ) ), ); $data = array_map( fn( Season $s ) => $s->toArray(), $defaults ); update_option( self::OPTION_NAME, $data ); } }