'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 );
}
}