rule_type = 'time'; // Load time-specific settings. if ( isset( $this->adjustments['recurring_type'] ) ) { $this->recurring_type = sanitize_text_field( $this->adjustments['recurring_type'] ); } if ( isset( $this->adjustments['recurring_days'] ) ) { $this->recurring_days = (array) $this->adjustments['recurring_days']; } if ( isset( $this->adjustments['time_restrictions'] ) ) { $this->time_restrictions = (array) $this->adjustments['time_restrictions']; } // Set timezone. $this->timezone = isset( $this->adjustments['timezone'] ) ? $this->adjustments['timezone'] : wp_timezone_string(); } /** * Check if rule is applicable. * * @param array $context Context data for evaluation. * @return bool */ public function is_applicable( $context ) { // Check if rule is active. if ( 'active' !== $this->status && 'scheduled' !== $this->status ) { return false; } // Check basic date range. if ( ! $this->is_within_date_range() ) { return false; } // Check recurring schedule. if ( ! $this->check_recurring_schedule() ) { return false; } // Check time of day restrictions. if ( ! $this->check_time_restrictions() ) { return false; } // Check additional conditions. if ( ! $this->check_conditions( $context ) ) { return false; } return true; } /** * Calculate price based on rule. * * @param float $original_price Original price. * @param array $context Context data. * @return float Modified price. */ public function calculate_price( $original_price, $context ) { // Get time-based adjustment. $adjustment = $this->get_time_based_adjustment(); if ( ! $adjustment ) { return $original_price; } // Apply the adjustment. return $this->apply_adjustment( $original_price, $adjustment ); } /** * Get display price for frontend. * * @param WC_Product $product Product object. * @param string $user_role User role. * @return float|string */ public function get_display_price( $product, $user_role = null ) { if ( ! $product ) { return ''; } // Build context. $context = array( 'product_id' => $product->get_id(), 'quantity' => 1, 'user_role' => $user_role ? $user_role : 'guest', 'timestamp' => current_time( 'timestamp' ), ); // Check if rule applies. if ( ! $this->is_applicable( $context ) ) { return $product->get_price(); } // Calculate and return price. return $this->calculate_price( $product->get_price(), $context ); } /** * Check recurring schedule. * * @return bool */ private function check_recurring_schedule() { if ( 'none' === $this->recurring_type ) { return true; } $current_time = current_time( 'timestamp' ); $timezone = new DateTimeZone( $this->timezone ); $datetime = new DateTime( '@' . $current_time ); $datetime->setTimezone( $timezone ); switch ( $this->recurring_type ) { case 'daily': // Always applies if daily. return true; case 'weekly': // Check day of week. $current_day = strtolower( $datetime->format( 'l' ) ); return in_array( $current_day, $this->recurring_days, true ); case 'monthly': // Check day of month. $current_date = (int) $datetime->format( 'j' ); return in_array( $current_date, $this->recurring_days, true ); case 'custom': // Custom schedule logic. return $this->check_custom_schedule( $datetime ); default: return true; } } /** * Check time of day restrictions. * * @return bool */ private function check_time_restrictions() { if ( empty( $this->time_restrictions ) ) { return true; } $current_time = current_time( 'timestamp' ); $timezone = new DateTimeZone( $this->timezone ); $datetime = new DateTime( '@' . $current_time ); $datetime->setTimezone( $timezone ); $current_hour = (int) $datetime->format( 'G' ); $current_minute = (int) $datetime->format( 'i' ); $current_time_minutes = $current_hour * 60 + $current_minute; foreach ( $this->time_restrictions as $restriction ) { $start_time = isset( $restriction['start_time'] ) ? $restriction['start_time'] : '00:00'; $end_time = isset( $restriction['end_time'] ) ? $restriction['end_time'] : '23:59'; // Convert to minutes. list( $start_hour, $start_minute ) = explode( ':', $start_time ); list( $end_hour, $end_minute ) = explode( ':', $end_time ); $start_minutes = (int) $start_hour * 60 + (int) $start_minute; $end_minutes = (int) $end_hour * 60 + (int) $end_minute; // Check if current time is within range. if ( $current_time_minutes >= $start_minutes && $current_time_minutes <= $end_minutes ) { return true; } } return false; } /** * Check custom schedule. * * @param DateTime $datetime Current datetime. * @return bool */ private function check_custom_schedule( $datetime ) { // This could be extended for complex custom schedules. // For example: every first Monday of the month, etc. /** * Filter to check custom schedule. * * @since 1.0.0 * * @param bool $result The schedule check result. * @param DateTime $datetime Current datetime object. * @param object $rule The time rule object. */ return apply_filters( 'dp_aicoso_check_custom_schedule', true, $datetime, $this ); } /** * Get time-based adjustment. * * @return array|null */ private function get_time_based_adjustment() { // Could have different adjustments for different times. if ( isset( $this->adjustments['default'] ) ) { return $this->adjustments['default']; } // Fallback to simple adjustment. return array( 'type' => isset( $this->adjustments['discount_type'] ) ? $this->adjustments['discount_type'] : 'percentage', 'value' => isset( $this->adjustments['discount_value'] ) ? floatval( $this->adjustments['discount_value'] ) : 0, ); } /** * Get time remaining for this rule. * * @return int|false Seconds remaining or false if not applicable. */ public function get_time_remaining() { if ( empty( $this->end_date ) ) { return false; } $end_timestamp = strtotime( $this->end_date ); $current_timestamp = current_time( 'timestamp' ); if ( $end_timestamp > $current_timestamp ) { return $end_timestamp - $current_timestamp; } return false; } /** * Get formatted time remaining. * * @return string */ public function get_formatted_time_remaining() { $seconds = $this->get_time_remaining(); if ( false === $seconds ) { return ''; } $days = floor( $seconds / 86400 ); $hours = floor( ( $seconds % 86400 ) / 3600 ); $minutes = floor( ( $seconds % 3600 ) / 60 ); $parts = array(); if ( $days > 0 ) { /* translators: %d: number of days */ $parts[] = sprintf( _n( '%d day', '%d days', $days, 'aicoso-dynamic-pricing-for-woocommerce' ), $days ); } if ( $hours > 0 ) { /* translators: %d: number of hours */ $parts[] = sprintf( _n( '%d hour', '%d hours', $hours, 'aicoso-dynamic-pricing-for-woocommerce' ), $hours ); } if ( $minutes > 0 ) { /* translators: %d: number of minutes */ $parts[] = sprintf( _n( '%d minute', '%d minutes', $minutes, 'aicoso-dynamic-pricing-for-woocommerce' ), $minutes ); } return implode( ', ', $parts ); } /** * Set recurring schedule. * * @param string $type Recurring type. * @param array $days Days for recurring. */ public function set_recurring_schedule( $type, $days = array() ) { $this->recurring_type = sanitize_text_field( $type ); $this->recurring_days = array_map( 'sanitize_text_field', (array) $days ); $this->adjustments['recurring_type'] = $this->recurring_type; $this->adjustments['recurring_days'] = $this->recurring_days; } /** * Set time restrictions. * * @param array $restrictions Time restrictions. */ public function set_time_restrictions( $restrictions ) { $this->time_restrictions = array(); foreach ( (array) $restrictions as $restriction ) { $this->time_restrictions[] = array( 'start_time' => isset( $restriction['start_time'] ) ? sanitize_text_field( $restriction['start_time'] ) : '00:00', 'end_time' => isset( $restriction['end_time'] ) ? sanitize_text_field( $restriction['end_time'] ) : '23:59', ); } $this->adjustments['time_restrictions'] = $this->time_restrictions; } /** * Get schedule information for display. * * @return array */ public function get_schedule_info() { $info = array( 'type' => $this->recurring_type, 'days' => $this->recurring_days, 'time_slots' => $this->time_restrictions, 'timezone' => $this->timezone, 'start_date' => $this->start_date, 'end_date' => $this->end_date, 'is_active' => $this->is_applicable( array() ), 'time_remaining' => $this->get_formatted_time_remaining(), ); // Add human-readable schedule. $info['schedule_text'] = $this->get_schedule_text(); return $info; } /** * Get human-readable schedule text. * * @return string */ private function get_schedule_text() { $text = ''; switch ( $this->recurring_type ) { case 'daily': $text = __( 'Daily', 'aicoso-dynamic-pricing-for-woocommerce' ); break; case 'weekly': if ( ! empty( $this->recurring_days ) ) { $text = sprintf( /* translators: %s: comma-separated list of days */ __( 'Every %s', 'aicoso-dynamic-pricing-for-woocommerce' ), implode( ', ', array_map( 'ucfirst', $this->recurring_days ) ) ); } break; case 'monthly': if ( ! empty( $this->recurring_days ) ) { $text = sprintf( /* translators: %s: comma-separated list of day numbers */ __( 'On day %s of each month', 'aicoso-dynamic-pricing-for-woocommerce' ), implode( ', ', $this->recurring_days ) ); } break; default: $text = __( 'Custom schedule', 'aicoso-dynamic-pricing-for-woocommerce' ); } // Add time restrictions. if ( ! empty( $this->time_restrictions ) ) { $time_parts = array(); foreach ( $this->time_restrictions as $restriction ) { $time_parts[] = sprintf( '%s - %s', $restriction['start_time'], $restriction['end_time'] ); } /* translators: %s: comma-separated list of time ranges */ $text .= ' ' . sprintf( __( 'at %s', 'aicoso-dynamic-pricing-for-woocommerce' ), implode( ', ', $time_parts ) ); } return $text; } /** * Check if rule is currently active based on schedule. * * @return bool */ public function is_currently_active() { return $this->is_applicable( array( 'timestamp' => current_time( 'timestamp' ) ) ); } }