'day_start_meridiem_offset' => 'am', 'day_end_hour' => '11', 'day_end_minute' => '59', 'day_end_meridiem_offset' => 'pm', 'time_to_use' => 'server', // server | custom. 'custom_timezone' => 'UTC', ), ); } // **************************************** // SCHEDULE. // **************************************** /** * Whether this module will be shown any time in the future. * Used in dashboard to display a notice if it won't. * * @since 4.2.0 * @return bool */ public function will_be_shown_again() { $is_scheduled = $this->is_currently_scheduled(); if ( ! $is_scheduled ) { $flags = $this->module->get_schedule_flags(); // The module isn't shown now and won't be checked to show later on. if ( '0' === $flags['check_schedule_at'] ) { return false; } } return true; } /** * Returns whether the "schedule" setting allows this module to be displayed * * @since 4.2.0 * @return boolean */ public function is_currently_scheduled() { $schedule_flags = $this->module->get_schedule_flags(); $is_active = '1' === $schedule_flags['is_currently_scheduled']; $check_schedule_at = $schedule_flags['check_schedule_at']; $current_time = time(); $skip_cache = false; /** * Whether to use cached flags to check schedule settings. * Useful for debugging. * * @since 4.2.0 * * @param bool $skip_cache Set to true if you want the schedule to be checked on each run. * @param Hustle_Module_Model $module */ $skip_cache = apply_filters( 'hustle_skip_schedule_cache', $skip_cache, $this->module ); // Run the check if the flag isn't set to "don't check again" // and the time the "schedule" should be checked again already passed. if ( $skip_cache || ( '0' !== $check_schedule_at && $current_time > $check_schedule_at ) ) { $is_active = $this->check_schedule(); } /** * Filter whether the schedule allows this module to be displayed. * * @since 4.2.0 * * @param bool $is_currently_scheduled Whether the module should be shown. * @param Hustle_Module_Model $module This module's instance. */ return apply_filters( 'hustle_module_is_currently_scheduled', $is_active, $this->module ); } /** * Returns the "schedule" check and saves the "schedule" flags for future use * * @since 4.2.0 * @return bool */ private function check_schedule() { $flags = $this->get_schedule_flags(); /** * Filter the schedule flags. * Change their values before it's stored and before it's used * to define whether the module should be shown in frontend. * * @since 4.2.0 * * @param array $flags Schedule flags. * [ * is_currently_scheduled => 1|0, // As strings. 1 to display the module, 0 otherwise. * check_schedule_at => timestamp|0 // As strings. Next time the schedule will be checked. 0 to skip check. * ] * @param Hustle_Meta_Base_Settings $this */ $flags = apply_filters( 'hustle_get_schedule_flags', $flags, $this ); // Store the flags for future use. $this->module->set_schedule_flags( $flags ); return ( '1' === $flags['is_currently_scheduled'] ); } /** * Returns the 'schedule flags' to be stored. * * @since 4.2.0 * @return array */ private function get_schedule_flags() { $settings = $this->to_array(); // Schedule is deactivated. Show it right away. if ( '1' !== $settings['is_schedule'] ) { // Skip schedule check in next runs. return array( 'is_currently_scheduled' => '1', 'check_schedule_at' => '0', ); } $schedule = $settings['schedule']; $start_strtotime_str = "{$schedule['start_date']} {$schedule['start_hour']}:{$schedule['start_minute']} {$schedule['start_meridiem_offset']}"; $schedule['start_timestamp'] = $this->get_time_with_timezone( $start_strtotime_str ); $end_strtotime_str = "{$schedule['end_date']} {$schedule['end_hour']}:{$schedule['end_minute']} {$schedule['end_meridiem_offset']}"; $schedule['end_timestamp'] = $this->get_time_with_timezone( $end_strtotime_str ); $is_within_date_range = $this->check_schedule_date_range( $schedule ); // We're not within the active date ranges. Return the flags to be set. if ( true !== $is_within_date_range ) { return $is_within_date_range; } $check_daily_range = $this->check_schedule_daily_range( $schedule ); // There's no daily range. if ( false === $check_daily_range ) { // Run the next check at the end of the date range, if set. $end_timestamp = '1' !== $schedule['not_schedule_end'] ? $schedule['end_timestamp'] : '0'; return array( 'is_currently_scheduled' => '1', 'check_schedule_at' => $end_timestamp, ); } return $check_daily_range; } /** * Checks whether we're within the date range. * * @since 4.2.0 * @param array $schedule Stored 'schedule' settings. * @return array|true Array if the module isn't active. True otherwise. */ private function check_schedule_date_range( $schedule ) { $current_time = time(); // End is scheduled. if ( '1' !== $schedule['not_schedule_end'] ) { $end_timestamp = $schedule['end_timestamp']; // End moment already passed. if ( $current_time > $end_timestamp ) { // Skip schedule check in the future. return array( 'is_currently_scheduled' => '0', 'check_schedule_at' => '0', ); } } // Start is scheduled. if ( '1' !== $schedule['not_schedule_start'] ) { $start_timestamp = $schedule['start_timestamp']; // Start moment hasn't passed yet. if ( $current_time < $start_timestamp ) { // Run the schedule check again then. return array( 'is_currently_scheduled' => '0', 'check_schedule_at' => $start_timestamp, ); } } return true; } /** * Check out the daily time range. * * @since 4.2.0 * @param array $schedule Stored 'schedule' settings. * @return array|false Array if there's a 'next check' scheduled. False otherwise. */ private function check_schedule_daily_range( $schedule ) { $check_day = 'all' !== $schedule['active_days'] && ! empty( $schedule['week_days'] ); $check_hour = '1' !== $schedule['is_active_all_day']; // The module is displayed all day every day. if ( ! $check_day && ! $check_hour ) { return false; } // Get today's week day as a number between 0 (Sun) and 6 (Mon). $current_time = time(); $todays_day = $this->get_time_with_timezone( 'now', 'w' ); $week_days = $schedule['week_days']; if ( $check_day ) { // Not displaying today. if ( ! in_array( $todays_day, $week_days, true ) ) { // Run the check again the next day it should be shown. $next_check = $this->get_schedule_next_week_day_timestamp( $todays_day, $week_days ); return array( 'is_currently_scheduled' => '0', 'check_schedule_at' => $next_check, ); } else { // Run the check again the next day it should be hidden. $next_check = $this->get_schedule_next_week_day_timestamp( $todays_day, array_diff( array( 1, 2, 3, 4, 5, 6, 7 ), $week_days ) ); $is_currently_scheduled = '1'; } } if ( $check_hour ) { $end_strtotime_str = "{$schedule['day_end_hour']}:{$schedule['day_end_minute']} {$schedule['day_end_meridiem_offset']}"; $end_timestamp = $this->get_time_with_timezone( $end_strtotime_str ); $start_strtotime_str = "{$schedule['day_start_hour']}:{$schedule['day_start_minute']} {$schedule['day_start_meridiem_offset']}"; $start_timestamp = $this->get_time_with_timezone( $start_strtotime_str ); // If start time is greater that end time swap them. $swaptime = $start_timestamp > $end_timestamp ? true : false; if ( $swaptime ) { // swap start time and end time. list( $start_strtotime_str, $end_strtotime_str ) = array( $end_strtotime_str, $start_strtotime_str ); list( $start_timestamp, $end_timestamp ) = array( $end_timestamp, $start_timestamp ); } // End time already passed. if ( $current_time > $end_timestamp ) { // Run the check again the next day it should be shown. if ( $check_day ) { $next_check = $this->get_schedule_next_week_day_timestamp( $todays_day, $week_days ); } else { $next_check = $this->get_time_with_timezone( 'tomorrow ' . $start_strtotime_str ); } return array( 'is_currently_scheduled' => $swaptime ? '1' : '0', 'check_schedule_at' => $next_check, ); } // Start time hasn't passed. if ( $current_time < $start_timestamp ) { // Run the check again at the time it should be shown again. $next_check = $this->get_time_with_timezone( $start_strtotime_str ); return array( 'is_currently_scheduled' => $swaptime ? '1' : '0', 'check_schedule_at' => $next_check, ); } // Start time already passed and end time hasn't passed. return array( 'is_currently_scheduled' => $swaptime ? '0' : '1', 'check_schedule_at' => $end_timestamp, ); } elseif ( $is_currently_scheduled ) { // Run the check again the next day it should be hidden. return array( 'is_currently_scheduled' => $is_currently_scheduled, 'check_schedule_at' => $next_check, ); } return false; } /** * Get the next day of the week when the module should be displayed. * * @since 4.2.0 * @param string $todays_day Today's week day as a 'w' format for date(). * @param array $week_days Selected weeks days for the module to be shown. * @return string timestamp */ private function get_schedule_next_week_day_timestamp( $todays_day, $week_days ) { // Get the following week day to be displayed. $next_day = false; foreach ( $week_days as $day ) { // Get the following one this week. if ( intval( $day ) > intval( $todays_day ) ) { $next_day = $day; $strtotime_str = "Sunday last week +{$next_day} days"; break; } } // If the next day to display isn't ahead this week, get the first selected week day. if ( false === $next_day ) { $next_day = $week_days[0]; $strtotime_str = "Sunday this week +{$next_day} days"; } // Run the check again the next day it should be shown. $next_check = $this->get_time_with_timezone( $strtotime_str ); return $next_check; } /** * Returns the given date string as Unix timestamp according to the selected wp timezone. * * @since 4.2.0 * @param string $str Time as a human readable string. * @param string $format Optional. Date format. * @return string Timestamp. */ private function get_time_with_timezone( $str = 'now', $format = 'U' ) { $settings = $this->to_array(); // We can probably make this a property of this class. // Using WP's selected timezone. if ( 'server' === $settings['schedule']['time_to_use'] ) { $timezone_string = $this->get_wp_timezone(); } else { $selected_timezone = $settings['schedule']['custom_timezone']; $timezone_string = $this->format_datetimezone_compatible_string( $selected_timezone ); } $timezone_object = new DateTimeZone( $timezone_string ); // return if the passed date is wrong. try { $date_time_instance = new DateTime( $str, $timezone_object ); } catch ( Exception $e ) { return ''; } return $date_time_instance->format( $format ); } /** * Gets the timezone set up in WordPress settings. * This timezone string is valid for the DateTimeZone constructor. * * @since 4.2.0 * * @return string */ private function get_wp_timezone() { // Available since WP 5.3.0. if ( function_exists( 'wp_timezone_string' ) ) { return wp_timezone_string(); } /** * Copied from @see https://developer.wordpress.org/reference/functions/wp_timezone_string/ * This is intended for WP versions previous to 5.3.0 */ $timezone_string = get_option( 'timezone_string' ); if ( $timezone_string ) { return $timezone_string; } $offset_str = get_option( 'gmt_offset' ); $tz_offset = $this->format_timezone_utc_offset( $offset_str ); return $tz_offset; } /** * Formats a timezone string so it's compatible with DateTimeZone. * * @since 4.2.0 * @param string $timezone_string Timezone string to format. * @return string */ private function format_datetimezone_compatible_string( $timezone_string ) { if ( '0' === $timezone_string ) { $timezone_string = 'UTC'; } if ( false === stripos( $timezone_string, 'UTC' ) ) { return $timezone_string; } $offset_str = str_replace( 'UTC', '', $timezone_string ); $tz_offset = $this->format_timezone_utc_offset( $offset_str ); return $tz_offset; } /** * Formats the utc offset so it's compatible with the DateTimezone constructor. * * @since 4.3.0 * @param string $offset_str UTC offset string. * @return string */ private function format_timezone_utc_offset( $offset_str ) { $offset = (float) $offset_str; $hours = (int) $offset; $minutes = ( $offset - $hours ); $sign = ( $offset < 0 ) ? '-' : '+'; $abs_hour = abs( $hours ); $abs_mins = abs( $minutes * 60 ); $tz_offset = sprintf( '%s%02d:%02d', $sign, $abs_hour, $abs_mins ); return $tz_offset; } /** * Check this module schedule has finished or not * Return true if schedule finished false otherwise * * @since 4.4.6 * @return boolean */ public function is_schedule_finished() { $settings = $this->to_array(); $schedule = $settings['schedule']; // If schedule is not enabled. if ( '0' === $settings['is_schedule'] ) { return false; } // If `Never end the schedule` option is enabled. if ( '1' === $schedule['not_schedule_end'] ) { return false; } $end_time_string = "{$schedule['end_date']} {$schedule['end_hour']}:{$schedule['end_minute']} {$schedule['end_meridiem_offset']}"; $end_timestamp = $this->get_time_with_timezone( $end_time_string ); // If time of today is already passed, then return true. return ( time() > $end_timestamp ); } /** * Return true if schedule is between start and end date * * @since 4.4.6 * @return boolean */ public function is_between_start_and_end_date() { $settings = $this->to_array(); $schedule = $settings['schedule']; // If schedule is not enabled. if ( '0' === $settings['is_schedule'] ) { return false; } $start_time_string = "{$schedule['start_date']} {$schedule['start_hour']}:{$schedule['start_minute']} {$schedule['start_meridiem_offset']}"; $start_timestamp = $this->get_time_with_timezone( $start_time_string ); $end_time_string = "{$schedule['end_date']} {$schedule['end_hour']}:{$schedule['end_minute']} {$schedule['end_meridiem_offset']}"; $end_timestamp = $this->get_time_with_timezone( $end_time_string ); // If time has been started but not finished, then return true. return ( time() >= $start_timestamp && time() <= $end_timestamp ); } }