'fas fa-video', 'title' => '', 'description' => '', 'version' => '', 'buttons' => array(), 'tabs' => array(), 'context' => 'settings', 'id' => '', 'notice_html' => '', ); $args = wp_parse_args( $args, $defaults ); if ( empty( $args['title'] ) ) { return ''; } $header_id = $args['id'] ? ' id="' . esc_attr( $args['id'] ) . '"' : ''; $context_class = 'plugetune-header-' . sanitize_html_class( $args['context'] ); ob_start(); ?>
>

label. */ public static function get_all_timezones() { return array( 'UTC' => 'UTC (UTC+0)', 'Africa/Cairo' => 'Africa/Cairo (Egypt - UTC+2)', 'Africa/Casablanca' => 'Africa/Casablanca (Morocco - UTC+1)', 'Africa/Johannesburg' => 'Africa/Johannesburg (South Africa - UTC+2)', 'Africa/Lagos' => 'Africa/Lagos (Nigeria - UTC+1)', 'Africa/Nairobi' => 'Africa/Nairobi (Kenya - UTC+3)', 'Africa/Tunis' => 'Africa/Tunis (Tunisia - UTC+1)', 'Asia/Dhaka' => 'Asia/Dhaka (Bangladesh - UTC+6)', 'Asia/Kolkata' => 'Asia/Kolkata (India - UTC+5:30)', 'Asia/Karachi' => 'Asia/Karachi (Pakistan - UTC+5)', 'Asia/Dubai' => 'Asia/Dubai (UAE - UTC+4)', 'Asia/Tokyo' => 'Asia/Tokyo (Japan - UTC+9)', 'Asia/Shanghai' => 'Asia/Shanghai (China - UTC+8)', 'Asia/Hong_Kong' => 'Asia/Hong_Kong (Hong Kong - UTC+8)', 'Asia/Singapore' => 'Asia/Singapore (Singapore - UTC+8)', 'Asia/Kuala_Lumpur' => 'Asia/Kuala_Lumpur (Malaysia - UTC+8)', 'Asia/Bangkok' => 'Asia/Bangkok (Thailand - UTC+7)', 'Asia/Jakarta' => 'Asia/Jakarta (Indonesia - UTC+7)', 'Asia/Seoul' => 'Asia/Seoul (South Korea - UTC+9)', 'Asia/Taipei' => 'Asia/Taipei (Taiwan - UTC+8)', 'Asia/Manila' => 'Asia/Manila (Philippines - UTC+8)', 'Asia/Ho_Chi_Minh' => 'Asia/Ho_Chi_Minh (Vietnam - UTC+7)', 'Asia/Rangoon' => 'Asia/Rangoon (Myanmar - UTC+6:30)', 'Asia/Colombo' => 'Asia/Colombo (Sri Lanka - UTC+5:30)', 'Asia/Kathmandu' => 'Asia/Kathmandu (Nepal - UTC+5:45)', 'Asia/Tehran' => 'Asia/Tehran (Iran - UTC+3:30)', 'Asia/Baghdad' => 'Asia/Baghdad (Iraq - UTC+3)', 'Asia/Riyadh' => 'Asia/Riyadh (Saudi Arabia - UTC+3)', 'Asia/Kuwait' => 'Asia/Kuwait (Kuwait - UTC+3)', 'Asia/Doha' => 'Asia/Doha (Qatar - UTC+3)', 'Asia/Muscat' => 'Asia/Muscat (Oman - UTC+4)', 'Asia/Yerevan' => 'Asia/Yerevan (Armenia - UTC+4)', 'Asia/Tbilisi' => 'Asia/Tbilisi (Georgia - UTC+4)', 'Asia/Baku' => 'Asia/Baku (Azerbaijan - UTC+4)', 'Asia/Amman' => 'Asia/Amman (Jordan - UTC+3)', 'Asia/Beirut' => 'Asia/Beirut (Lebanon - UTC+3)', 'Asia/Damascus' => 'Asia/Damascus (Syria - UTC+3)', 'Asia/Jerusalem' => 'Asia/Jerusalem (Israel - UTC+3)', 'Asia/Gaza' => 'Asia/Gaza (Palestine - UTC+3)', 'Asia/Kabul' => 'Asia/Kabul (Afghanistan - UTC+4:30)', 'Asia/Yekaterinburg' => 'Asia/Yekaterinburg (Russia - UTC+5)', 'Asia/Novosibirsk' => 'Asia/Novosibirsk (Russia - UTC+7)', 'Asia/Krasnoyarsk' => 'Asia/Krasnoyarsk (Russia - UTC+7)', 'Asia/Irkutsk' => 'Asia/Irkutsk (Russia - UTC+8)', 'Asia/Vladivostok' => 'Asia/Vladivostok (Russia - UTC+10)', 'Asia/Kamchatka' => 'Asia/Kamchatka (Russia - UTC+12)', 'Asia/Anadyr' => 'Asia/Anadyr (Russia - UTC+12)', 'Europe/London' => 'Europe/London (UK - UTC+0/UTC+1)', 'Europe/Berlin' => 'Europe/Berlin (Germany - UTC+1/UTC+2)', 'Europe/Paris' => 'Europe/Paris (France - UTC+1/UTC+2)', 'Europe/Rome' => 'Europe/Rome (Italy - UTC+1/UTC+2)', 'Europe/Madrid' => 'Europe/Madrid (Spain - UTC+1/UTC+2)', 'Europe/Amsterdam' => 'Europe/Amsterdam (Netherlands - UTC+1/UTC+2)', 'Europe/Brussels' => 'Europe/Brussels (Belgium - UTC+1/UTC+2)', 'Europe/Vienna' => 'Europe/Vienna (Austria - UTC+1/UTC+2)', 'Europe/Zurich' => 'Europe/Zurich (Switzerland - UTC+1/UTC+2)', 'Europe/Stockholm' => 'Europe/Stockholm (Sweden - UTC+1/UTC+2)', 'Europe/Oslo' => 'Europe/Oslo (Norway - UTC+1/UTC+2)', 'Europe/Copenhagen' => 'Europe/Copenhagen (Denmark - UTC+1/UTC+2)', 'Europe/Helsinki' => 'Europe/Helsinki (Finland - UTC+2/UTC+3)', 'Europe/Tallinn' => 'Europe/Tallinn (Estonia - UTC+2/UTC+3)', 'Europe/Riga' => 'Europe/Riga (Latvia - UTC+2/UTC+3)', 'Europe/Vilnius' => 'Europe/Vilnius (Lithuania - UTC+2/UTC+3)', 'Europe/Warsaw' => 'Europe/Warsaw (Poland - UTC+1/UTC+2)', 'Europe/Prague' => 'Europe/Prague (Czech Republic - UTC+1/UTC+2)', 'Europe/Budapest' => 'Europe/Budapest (Hungary - UTC+1/UTC+2)', 'Europe/Sofia' => 'Europe/Sofia (Bulgaria - UTC+2/UTC+3)', 'Europe/Bucharest' => 'Europe/Bucharest (Romania - UTC+2/UTC+3)', 'Europe/Athens' => 'Europe/Athens (Greece - UTC+2/UTC+3)', 'Europe/Istanbul' => 'Europe/Istanbul (Turkey - UTC+3)', 'Europe/Kiev' => 'Europe/Kiev (Ukraine - UTC+2/UTC+3)', 'Europe/Minsk' => 'Europe/Minsk (Belarus - UTC+3)', 'Europe/Moscow' => 'Europe/Moscow (Russia - UTC+3)', 'Europe/Dublin' => 'Europe/Dublin (Ireland - UTC+0/UTC+1)', 'Europe/Lisbon' => 'Europe/Lisbon (Portugal - UTC+0/UTC+1)', 'Europe/Belgrade' => 'Europe/Belgrade (Serbia - UTC+1/UTC+2)', 'Europe/Zagreb' => 'Europe/Zagreb (Croatia - UTC+1/UTC+2)', 'Europe/Sarajevo' => 'Europe/Sarajevo (Bosnia - UTC+1/UTC+2)', 'Europe/Skopje' => 'Europe/Skopje (North Macedonia - UTC+1/UTC+2)', 'Europe/Tirane' => 'Europe/Tirane (Albania - UTC+1/UTC+2)', 'Europe/Reykjavik' => 'Europe/Reykjavik (Iceland - UTC+0)', 'America/New_York' => 'America/New_York (US Eastern - UTC-5/UTC-4)', 'America/Chicago' => 'America/Chicago (US Central - UTC-6/UTC-5)', 'America/Denver' => 'America/Denver (US Mountain - UTC-7/UTC-6)', 'America/Los_Angeles' => 'America/Los_Angeles (US Pacific - UTC-8/UTC-7)', 'America/Anchorage' => 'America/Anchorage (US Alaska - UTC-9/UTC-8)', 'America/Adak' => 'America/Adak (US Aleutian - UTC-10/UTC-9)', 'America/Phoenix' => 'America/Phoenix (US Arizona - UTC-7)', 'America/Detroit' => 'America/Detroit (US Michigan - UTC-5/UTC-4)', 'America/Indiana/Indianapolis' => 'America/Indiana/Indianapolis (US Indiana - UTC-5/UTC-4)', 'America/Indiana/Knox' => 'America/Indiana/Knox (US Indiana - UTC-6/UTC-5)', 'America/Kentucky/Louisville' => 'America/Kentucky/Louisville (US Kentucky - UTC-5/UTC-4)', 'America/North_Dakota/Center' => 'America/North_Dakota/Center (US North Dakota - UTC-6/UTC-5)', 'America/Toronto' => 'America/Toronto (Canada Eastern - UTC-5/UTC-4)', 'America/Montreal' => 'America/Montreal (Canada Eastern - UTC-5/UTC-4)', 'America/Vancouver' => 'America/Vancouver (Canada Pacific - UTC-8/UTC-7)', 'America/Edmonton' => 'America/Edmonton (Canada Mountain - UTC-7/UTC-6)', 'America/Winnipeg' => 'America/Winnipeg (Canada Central - UTC-6/UTC-5)', 'America/Halifax' => 'America/Halifax (Canada Atlantic - UTC-4/UTC-3)', 'America/St_Johns' => 'America/St_Johns (Canada Newfoundland - UTC-3:30/UTC-2:30)', 'America/Mexico_City' => 'America/Mexico_City (Mexico - UTC-6)', 'America/Tijuana' => 'America/Tijuana (Mexico - UTC-8/UTC-7)', 'America/Guatemala' => 'America/Guatemala (Guatemala - UTC-6)', 'America/El_Salvador' => 'America/El_Salvador (El Salvador - UTC-6)', 'America/Tegucigalpa' => 'America/Tegucigalpa (Honduras - UTC-6)', 'America/Managua' => 'America/Managua (Nicaragua - UTC-6)', 'America/Costa_Rica' => 'America/Costa_Rica (Costa Rica - UTC-6)', 'America/Panama' => 'America/Panama (Panama - UTC-5)', 'America/Havana' => 'America/Havana (Cuba - UTC-5/UTC-4)', 'America/Santo_Domingo' => 'America/Santo_Domingo (Dominican Republic - UTC-4)', 'America/Puerto_Rico' => 'America/Puerto_Rico (Puerto Rico - UTC-4)', 'America/Jamaica' => 'America/Jamaica (Jamaica - UTC-5)', 'America/Port-au-Prince' => 'America/Port-au-Prince (Haiti - UTC-5/UTC-4)', 'America/Nassau' => 'America/Nassau (Bahamas - UTC-5/UTC-4)', 'America/Sao_Paulo' => 'America/Sao_Paulo (Brazil - UTC-3)', 'America/Rio_de_Janeiro' => 'America/Rio_de_Janeiro (Brazil - UTC-3)', 'America/Recife' => 'America/Recife (Brazil - UTC-3)', 'America/Manaus' => 'America/Manaus (Brazil - UTC-4)', 'America/Belem' => 'America/Belem (Brazil - UTC-3)', 'America/Cuiaba' => 'America/Cuiaba (Brazil - UTC-4)', 'America/Porto_Velho' => 'America/Porto_Velho (Brazil - UTC-4)', 'America/Boa_Vista' => 'America/Boa_Vista (Brazil - UTC-4)', 'America/Fortaleza' => 'America/Fortaleza (Brazil - UTC-3)', 'America/Maceio' => 'America/Maceio (Brazil - UTC-3)', 'America/Bahia' => 'America/Bahia (Brazil - UTC-3)', 'America/Buenos_Aires' => 'America/Buenos_Aires (Argentina - UTC-3)', 'America/Montevideo' => 'America/Montevideo (Uruguay - UTC-3)', 'America/Asuncion' => 'America/Asuncion (Paraguay - UTC-4/UTC-3)', 'America/La_Paz' => 'America/La_Paz (Bolivia - UTC-4)', 'America/Lima' => 'America/Lima (Peru - UTC-5)', 'America/Quito' => 'America/Quito (Ecuador - UTC-5)', 'America/Caracas' => 'America/Caracas (Venezuela - UTC-4)', 'America/Bogota' => 'America/Bogota (Colombia - UTC-5)', 'America/Santiago' => 'America/Santiago (Chile - UTC-4/UTC-3)', 'America/Punta_Arenas' => 'America/Punta_Arenas (Chile - UTC-3)', 'Australia/Sydney' => 'Australia/Sydney (UTC+10/UTC+11)', 'Australia/Melbourne' => 'Australia/Melbourne (UTC+10/UTC+11)', 'Australia/Brisbane' => 'Australia/Brisbane (UTC+10)', 'Australia/Adelaide' => 'Australia/Adelaide (UTC+9:30/UTC+10:30)', 'Australia/Darwin' => 'Australia/Darwin (UTC+9:30)', 'Australia/Perth' => 'Australia/Perth (UTC+8)', 'Australia/Hobart' => 'Australia/Hobart (UTC+10/UTC+11)', 'Australia/Canberra' => 'Australia/Canberra (UTC+10/UTC+11)', 'Pacific/Auckland' => 'Pacific/Auckland (New Zealand - UTC+12/UTC+13)', 'Pacific/Chatham' => 'Pacific/Chatham (New Zealand - UTC+12:45/UTC+13:45)', 'Pacific/Fiji' => 'Pacific/Fiji (Fiji - UTC+12)', 'Pacific/Guam' => 'Pacific/Guam (Guam - UTC+10)', 'Pacific/Honolulu' => 'Pacific/Honolulu (Hawaii - UTC-10)', 'Pacific/Pago_Pago' => 'Pacific/Pago_Pago (American Samoa - UTC-11)', 'Pacific/Noumea' => 'Pacific/Noumea (New Caledonia - UTC+11)', 'Pacific/Port_Moresby' => 'Pacific/Port_Moresby (Papua New Guinea - UTC+10)', 'Indian/Mauritius' => 'Indian/Mauritius (Mauritius - UTC+4)', 'Indian/Maldives' => 'Indian/Maldives (Maldives - UTC+5)', 'Indian/Reunion' => 'Indian/Reunion (Reunion - UTC+4)', ); } /** * Normalize a timezone string to a valid PHP timezone identifier. * * @param string $timezone Timezone string to normalize. * @return string Valid PHP timezone identifier. */ public static function sanitize_timezone( $timezone ) { if ( strpos( $timezone, '/' ) !== false ) { return $timezone; } $utc_mappings = array( 'UTC+6' => 'Asia/Dhaka', 'UTC+5:30' => 'Asia/Kolkata', 'UTC+5' => 'Asia/Karachi', 'UTC+4' => 'Asia/Dubai', 'UTC+3' => 'Asia/Riyadh', 'UTC+9' => 'Asia/Tokyo', 'UTC+8' => 'Asia/Shanghai', 'UTC+10' => 'Australia/Sydney', 'UTC+12' => 'Pacific/Auckland', 'UTC-5' => 'America/New_York', 'UTC-6' => 'America/Chicago', 'UTC-7' => 'America/Denver', 'UTC-8' => 'America/Los_Angeles', 'UTC+0' => 'UTC', 'UTC' => 'UTC', ); if ( isset( $utc_mappings[ $timezone ] ) ) { return $utc_mappings[ $timezone ]; } return self::DEFAULT_TIMEZONE; } /** * Retrieve the human-readable label for a timezone. * * @param string $timezone Timezone identifier. * @return string Human-readable timezone label. */ public static function get_timezone_label( $timezone ) { $all_zones = self::get_all_timezones(); return isset( $all_zones[ $timezone ] ) ? $all_zones[ $timezone ] : $timezone; } /** * Detect the site's timezone using WordPress's built-in setting. * * @return array { * @type string $timezone Detected timezone identifier. * @type string $timezone_label Human-readable timezone label. * } */ public static function detect_timezone_from_ip() { $timezone = self::get_wp_timezone_string(); $valid_zones = DateTimeZone::listIdentifiers(); if ( ! in_array( $timezone, $valid_zones, true ) ) { $timezone = self::DEFAULT_TIMEZONE; } return array( 'timezone' => $timezone, 'timezone_label' => self::get_timezone_label( $timezone ), ); } /** * Retrieve the current time in the specified timezone. * * @param string|null $timezone_string Timezone identifier. * @return DateTime Current DateTime object in the specified timezone. */ public static function get_current_time_in_timezone( $timezone_string = null ) { if ( ! $timezone_string ) { $timezone_string = get_option( 'plugetune_email_timezone', self::DEFAULT_TIMEZONE ); } $timezone_string = self::sanitize_timezone( $timezone_string ); try { $timezone = new DateTimeZone( $timezone_string ); return new DateTime( 'now', $timezone ); } catch ( Exception $e ) { return new DateTime( 'now' ); } } /** * Format a timestamp in the specified timezone. * * @param int $timestamp Unix timestamp. * @param string|null $timezone_string Timezone identifier. * @param string $format Date format. Default 'Y-m-d H:i:s'. * @return string Formatted date string. */ public static function format_in_timezone( $timestamp, $timezone_string = null, $format = 'Y-m-d H:i:s' ) { if ( ! $timezone_string ) { $timezone_string = get_option( 'plugetune_email_timezone', self::DEFAULT_TIMEZONE ); } $timezone_string = self::sanitize_timezone( $timezone_string ); try { $timezone = new DateTimeZone( $timezone_string ); $datetime = new DateTime( '@' . $timestamp ); $datetime->setTimezone( $timezone ); return $datetime->format( $format ); } catch ( Exception $e ) { return gmdate( $format, $timestamp ); } } /** * Parse a time string into a 24-hour hour/minute pair. * * @param string $time_string Time string to parse. * @return array Associative array with 'hour' and 'minute' keys. */ public static function parse_time_to_24hour( $time_string ) { $time_string = trim( $time_string ); $result = array( 'hour' => 23, 'minute' => 59, ); if ( preg_match( '/^([0-9]{1,2}):([0-9]{2})$/', $time_string, $matches ) ) { $hour = intval( $matches[1] ); $minute = intval( $matches[2] ); if ( $hour >= 0 && $hour <= 23 && $minute >= 0 && $minute <= 59 ) { return array( 'hour' => $hour, 'minute' => $minute, ); } } $has_am = stripos( $time_string, 'AM' ) !== false; $has_pm = stripos( $time_string, 'PM' ) !== false; $clean_time = str_ireplace( array( 'AM', 'PM' ), '', $time_string ); $clean_time = trim( $clean_time ); if ( preg_match( '/(\d{1,2}):(\d{2})/', $clean_time, $matches ) ) { $hour = intval( $matches[1] ); $minute = intval( $matches[2] ); if ( $has_am && 12 === $hour ) { $hour = 0; } elseif ( $has_pm && 12 !== $hour ) { $hour += 12; } $result['hour'] = $hour; $result['minute'] = $minute; } return $result; } /** * Convert a 24-hour time string to 12-hour format with AM/PM. * * @param string $time_24h Time in 24-hour format (HH:MM). * @return string Time in 12-hour format with AM/PM. */ public static function convert_24h_to_12h_format( $time_24h ) { if ( empty( $time_24h ) ) { return '09:00 AM'; } if ( stripos( $time_24h, 'AM' ) !== false || stripos( $time_24h, 'PM' ) !== false ) { return $time_24h; } $time_parts = explode( ':', $time_24h ); if ( count( $time_parts ) < 2 ) { return $time_24h; } $hour = intval( $time_parts[0] ); $minute = isset( $time_parts[1] ) ? intval( $time_parts[1] ) : 0; $period = $hour >= 12 ? 'PM' : 'AM'; $hour_12 = $hour % 12; if ( 0 === $hour_12 ) { $hour_12 = 12; } return sprintf( '%d:%02d %s', $hour_12, $minute, $period ); } /** * Build a DateTimeZone from a stored timezone string (never throws). * * @param string|null $timezone_string Timezone identifier. * @return DateTimeZone */ private static function get_timezone_object( $timezone_string = null ) { if ( ! $timezone_string ) { $timezone_string = get_option( 'plugetune_email_timezone', self::DEFAULT_TIMEZONE ); } try { return new DateTimeZone( self::sanitize_timezone( $timezone_string ) ); } catch ( Exception $e ) { return new DateTimeZone( self::DEFAULT_TIMEZONE ); } } /** * Compute the report timestamp (UTC) of the current day / week. * * @param string $frequency 'daily' or 'weekly'. * @param string|null $time_string Time of day (e.g. "11:59 PM"); defaults to the saved option. * @param string|null $timezone_string Timezone identifier. * @param string|null $weekly_day Weekday name for weekly reports; defaults to the saved option. * @return int|null */ private static function get_schedule_timestamp_for_period( $frequency, $time_string = null, $timezone_string = null, $weekly_day = null ) { $tz = self::get_timezone_object( $timezone_string ); if ( 'daily' === $frequency ) { if ( null === $time_string || '' === $time_string ) { $time_string = get_option( 'plugetune_daily_report_time', '11:59 PM' ); } } elseif ( 'weekly' === $frequency ) { if ( null === $time_string || '' === $time_string ) { $time_string = get_option( 'plugetune_weekly_report_time', '09:00 AM' ); } if ( ! $weekly_day ) { $weekly_day = get_option( 'plugetune_weekly_report_day', 'monday' ); } } else { return null; } $time = self::parse_time_to_24hour( $time_string ); $date = new DateTime( 'now', $tz ); if ( 'weekly' === $frequency ) { $offsets = array( 'monday' => 0, 'tuesday' => 1, 'wednesday' => 2, 'thursday' => 3, 'friday' => 4, 'saturday' => 5, 'sunday' => 6, ); $day = strtolower( (string) $weekly_day ); $offset = isset( $offsets[ $day ] ) ? $offsets[ $day ] : 0; $date->modify( 'monday this week' ); if ( $offset > 0 ) { $date->modify( '+' . $offset . ' days' ); } } $date->setTime( (int) $time['hour'], (int) $time['minute'], 0 ); return $date->getTimestamp(); } /** * Timestamp (UTC) at which today's / this week's report is (or was) due. * * @param string $frequency 'daily' or 'weekly'. * @return int|null */ public static function get_todays_schedule_timestamp( $frequency ) { return self::get_schedule_timestamp_for_period( $frequency ); } /** * Timestamp (UTC) of the next report that will be sent. * * @param string $frequency 'daily' or 'weekly'. * @param string|null $time_override Optional time of day overriding the saved one. * @return int|null */ public static function get_next_scheduled_timestamp( $frequency, $time_override = null ) { $timestamp = self::get_schedule_timestamp_for_period( $frequency, $time_override ); if ( null === $timestamp ) { return null; } if ( $timestamp <= time() ) { $timestamp += ( 'weekly' === $frequency ) ? WEEK_IN_SECONDS : DAY_IN_SECONDS; } return $timestamp; } /** * Render the Schedule Preview HTML shared by the settings page and its AJAX refresh. * * @param string $type 'daily', 'weekly' or 'once'. * @param array $params timezone, daily_time, weekly_day, weekly_time, datetime. * @return string HTML (already escaped). */ public static function render_schedule_preview_shared( $type, $params = array() ) { $timezone_string = isset( $params['timezone'] ) ? self::sanitize_timezone( $params['timezone'] ) : get_option( 'plugetune_email_timezone', self::DEFAULT_TIMEZONE ); $tz = self::get_timezone_object( $timezone_string ); $format = 'F j, Y \a\t g:i a'; $next_ts = null; $summary = ''; if ( 'daily' === $type ) { $time_string = ! empty( $params['daily_time'] ) ? $params['daily_time'] : '11:59 PM'; $next_ts = self::get_next_ts_for( 'daily', $time_string, $timezone_string, null ); $summary = sprintf( /* translators: 1: time of day, 2: timezone */ __( 'Every day at %1$s (%2$s)', 'plugetune' ), self::format_time_12h( $time_string ), $timezone_string ); } elseif ( 'weekly' === $type ) { $time_string = ! empty( $params['weekly_time'] ) ? $params['weekly_time'] : '09:00 AM'; $day = ! empty( $params['weekly_day'] ) ? $params['weekly_day'] : 'monday'; $next_ts = self::get_next_ts_for( 'weekly', $time_string, $timezone_string, $day ); $summary = sprintf( /* translators: 1: weekday, 2: time of day, 3: timezone */ __( 'Every %1$s at %2$s (%3$s)', 'plugetune' ), ucfirst( strtolower( $day ) ), self::format_time_12h( $time_string ), $timezone_string ); } elseif ( 'once' === $type ) { $datetime = ! empty( $params['datetime'] ) ? $params['datetime'] : ''; try { $next_ts = $datetime ? ( new DateTime( $datetime, $tz ) )->getTimestamp() : null; } catch ( Exception $e ) { $next_ts = null; } $summary = $next_ts ? sprintf( /* translators: 1: date and time, 2: timezone */ __( 'Once on %1$s (%2$s)', 'plugetune' ), ( new DateTime( '@' . $next_ts ) )->setTimezone( $tz )->format( 'F j, Y \a\t h:i A' ), $timezone_string ) : __( 'One-time report', 'plugetune' ); } else { return '' . esc_html__( 'Schedule disabled', 'plugetune' ) . ''; } $html = ' ' . esc_html( $summary ); if ( $next_ts ) { $html .= ' (' . esc_html( self::format_time_until( $next_ts - time() ) ) . ')'; } $html .= ''; return $html; } /** * Format a time string as "09:00 AM". * * @param string $time_string Time string (12h or 24h). * @return string */ public static function format_time_12h( $time_string ) { $t = self::parse_time_to_24hour( $time_string ); return sprintf( '%02d:%02d %s', ( (int) $t['hour'] % 12 ) ? ( (int) $t['hour'] % 12 ) : 12, (int) $t['minute'], ( (int) $t['hour'] >= 12 ) ? 'PM' : 'AM' ); } /** * Human readable countdown, e.g. "in 11 hours 56 minutes". * * @param int $seconds Seconds until the event. * @return string */ public static function format_time_until( $seconds ) { $seconds = max( 0, (int) $seconds ); $days = (int) floor( $seconds / DAY_IN_SECONDS ); $hours = (int) floor( ( $seconds % DAY_IN_SECONDS ) / HOUR_IN_SECONDS ); $minutes = (int) floor( ( $seconds % HOUR_IN_SECONDS ) / MINUTE_IN_SECONDS ); $parts = array(); if ( $days > 0 ) { /* translators: %d: number of days */ $parts[] = sprintf( _n( '%d day', '%d days', $days, 'plugetune' ), $days ); } if ( $hours > 0 ) { /* translators: %d: number of hours */ $parts[] = sprintf( _n( '%d hour', '%d hours', $hours, 'plugetune' ), $hours ); } if ( $minutes > 0 || empty( $parts ) ) { /* translators: %d: number of minutes */ $parts[] = sprintf( _n( '%d minute', '%d minutes', $minutes, 'plugetune' ), $minutes ); } /* translators: %s: countdown, e.g. "11 hours 56 minutes" */ return sprintf( __( 'in %s', 'plugetune' ), implode( ' ', $parts ) ); } /** * Next occurrence timestamp for explicit schedule values. * * @param string $frequency 'daily' or 'weekly'. * @param string $time_string Time of day. * @param string $timezone_string Timezone identifier. * @param string|null $weekly_day Weekday name for weekly reports. * @return int|null */ private static function get_next_ts_for( $frequency, $time_string, $timezone_string, $weekly_day ) { $timestamp = self::get_schedule_timestamp_for_period( $frequency, $time_string, $timezone_string, $weekly_day ); if ( null === $timestamp ) { return null; } if ( $timestamp <= time() ) { $timestamp += ( 'weekly' === $frequency ) ? WEEK_IN_SECONDS : DAY_IN_SECONDS; } return $timestamp; } /** * Increment the tracked total video import count. * * @param int $count Number of videos to add. * @return void */ public static function track_video_import( $count ) { if ( empty( $count ) ) { return; } $total = (int) get_option( 'plugetune_total_video_imports', 0 ); $total += $count; update_option( 'plugetune_total_video_imports', $total ); } }