ComponentHelper.php
20 hours ago
DateTimeHelper.php
20 hours ago
HttpHelper.php
11 months ago
PluginInstaller.php
1 year ago
QueryHelper.php
20 hours ago
SessionHelper.php
3 years ago
TimerHelper.php
20 hours ago
UrlHelper.php
20 hours ago
ValidationHelper.php
10 months ago
TimerHelper.php
275 lines
| 1 | <?php |
| 2 | /** |
| 3 | * Tutor LMS Timer Utility |
| 4 | * |
| 5 | * Clean, reusable countdown formatter using PHP built-in DateTime APIs. |
| 6 | * |
| 7 | * Features: |
| 8 | * - Uses DateInterval / DateTimeImmutable |
| 9 | * - Multiple formats |
| 10 | * - Token generation |
| 11 | * - Render helper |
| 12 | * - No mixed business/render logic |
| 13 | * - Stable/reusable API |
| 14 | * |
| 15 | * @package Tutor\Helper |
| 16 | * @author Themeum <support@themeum.com> |
| 17 | * @link https://themeum.com |
| 18 | * @since 4.0.0 |
| 19 | */ |
| 20 | |
| 21 | namespace Tutor\Helpers; |
| 22 | |
| 23 | defined( 'ABSPATH' ) || exit; |
| 24 | |
| 25 | use DateTimeImmutable; |
| 26 | |
| 27 | /** |
| 28 | * TimerHelper class |
| 29 | * |
| 30 | * @since 4.0.0 |
| 31 | */ |
| 32 | final class TimerHelper { |
| 33 | |
| 34 | /** |
| 35 | * Convert seconds into normalized time parts. |
| 36 | * |
| 37 | * @param int $seconds Total seconds. |
| 38 | * |
| 39 | * @return array{ |
| 40 | * days:int, |
| 41 | * hours:int, |
| 42 | * minutes:int, |
| 43 | * seconds:int, |
| 44 | * total_hours:int |
| 45 | * } |
| 46 | */ |
| 47 | public static function decompose( int $seconds ): array { |
| 48 | |
| 49 | $seconds = max( 0, $seconds ); |
| 50 | |
| 51 | $start = new DateTimeImmutable( '@0' ); |
| 52 | $end = new DateTimeImmutable( '@' . $seconds ); |
| 53 | |
| 54 | $diff = $start->diff( $end ); |
| 55 | |
| 56 | $total_hours = (int) floor( $seconds / HOUR_IN_SECONDS ); |
| 57 | |
| 58 | return array( |
| 59 | 'days' => (int) $diff->days, |
| 60 | 'hours' => (int) $diff->h, |
| 61 | 'minutes' => (int) $diff->i, |
| 62 | 'seconds' => (int) $diff->s, |
| 63 | 'total_hours' => $total_hours, |
| 64 | ); |
| 65 | } |
| 66 | |
| 67 | /** |
| 68 | * Format as digital timer. |
| 69 | * |
| 70 | * Examples: |
| 71 | * - 05:12 |
| 72 | * - 01:05:12 |
| 73 | * - 2d 01:05:12 |
| 74 | * |
| 75 | * @param int $seconds Total seconds. |
| 76 | * @param bool $show_days Whether to show day segment. |
| 77 | * |
| 78 | * @return string |
| 79 | */ |
| 80 | public static function format_digital( |
| 81 | int $seconds, |
| 82 | bool $show_days = true |
| 83 | ): string { |
| 84 | |
| 85 | $time = self::decompose( $seconds ); |
| 86 | |
| 87 | $days = $time['days']; |
| 88 | $hours = $time['hours']; |
| 89 | $minutes = $time['minutes']; |
| 90 | $secs = $time['seconds']; |
| 91 | |
| 92 | // Show days. |
| 93 | if ( $show_days && $days > 0 ) { |
| 94 | return sprintf( |
| 95 | '%dd %02d:%02d:%02d', |
| 96 | $days, |
| 97 | $hours, |
| 98 | $minutes, |
| 99 | $secs |
| 100 | ); |
| 101 | } |
| 102 | |
| 103 | // Show hours. |
| 104 | if ( $time['total_hours'] > 0 ) { |
| 105 | return sprintf( |
| 106 | '%02d:%02d:%02d', |
| 107 | $time['total_hours'], |
| 108 | $minutes, |
| 109 | $secs |
| 110 | ); |
| 111 | } |
| 112 | |
| 113 | // Minutes only. |
| 114 | return sprintf( |
| 115 | '%02d:%02d', |
| 116 | $minutes, |
| 117 | $secs |
| 118 | ); |
| 119 | } |
| 120 | |
| 121 | /** |
| 122 | * Format as verbose human readable string. |
| 123 | * |
| 124 | * Examples: |
| 125 | * - 2 days 3 hours 5 minutes |
| 126 | * - 12 minutes 10 seconds |
| 127 | * |
| 128 | * @param int $seconds Total seconds. |
| 129 | * |
| 130 | * @return string |
| 131 | */ |
| 132 | public static function format_human( int $seconds ): string { |
| 133 | |
| 134 | $time = self::decompose( $seconds ); |
| 135 | |
| 136 | $parts = array(); |
| 137 | |
| 138 | if ( $time['days'] > 0 ) { |
| 139 | $parts[] = sprintf( |
| 140 | /* translators: %d: number of days */ |
| 141 | _n( '%d day', '%d days', $time['days'], 'tutor' ), |
| 142 | $time['days'] |
| 143 | ); |
| 144 | } |
| 145 | |
| 146 | if ( $time['hours'] > 0 ) { |
| 147 | $parts[] = sprintf( |
| 148 | /* translators: %d: number of hours */ |
| 149 | _n( '%d hour', '%d hours', $time['hours'], 'tutor' ), |
| 150 | $time['hours'] |
| 151 | ); |
| 152 | } |
| 153 | |
| 154 | if ( $time['minutes'] > 0 ) { |
| 155 | $parts[] = sprintf( |
| 156 | /* translators: %d: number of minutes */ |
| 157 | _n( '%d minute', '%d minutes', $time['minutes'], 'tutor' ), |
| 158 | $time['minutes'] |
| 159 | ); |
| 160 | } |
| 161 | |
| 162 | if ( $time['seconds'] > 0 || empty( $parts ) ) { |
| 163 | $parts[] = sprintf( |
| 164 | /* translators: %d: number of seconds */ |
| 165 | _n( '%d second', '%d seconds', $time['seconds'], 'tutor' ), |
| 166 | $time['seconds'] |
| 167 | ); |
| 168 | } |
| 169 | |
| 170 | return implode( ' ', $parts ); |
| 171 | } |
| 172 | |
| 173 | /** |
| 174 | * Build render able timer tokens. |
| 175 | * |
| 176 | * Example output: |
| 177 | * [ |
| 178 | * ['type' => 'digit', 'value' => '0'], |
| 179 | * ['type' => 'digit', 'value' => '2'], |
| 180 | * ['type' => 'separator', 'value' => ':'], |
| 181 | * ] |
| 182 | * |
| 183 | * @param int $seconds Total seconds. |
| 184 | * |
| 185 | * @return array |
| 186 | */ |
| 187 | public static function build_tokens( int $seconds ): array { |
| 188 | |
| 189 | $formatted = self::format_digital( $seconds ); |
| 190 | |
| 191 | $tokens = array(); |
| 192 | |
| 193 | $chars = preg_split( '//u', $formatted, -1, PREG_SPLIT_NO_EMPTY ); |
| 194 | |
| 195 | foreach ( $chars as $char ) { |
| 196 | |
| 197 | if ( ':' === $char ) { |
| 198 | |
| 199 | $tokens[] = array( |
| 200 | 'type' => 'separator', |
| 201 | 'value' => $char, |
| 202 | ); |
| 203 | |
| 204 | } elseif ( ctype_digit( $char ) ) { |
| 205 | |
| 206 | $tokens[] = array( |
| 207 | 'type' => 'digit', |
| 208 | 'value' => $char, |
| 209 | ); |
| 210 | |
| 211 | } elseif ( 'd' === $char ) { |
| 212 | |
| 213 | $tokens[] = array( |
| 214 | 'type' => 'suffix', |
| 215 | 'value' => $char, |
| 216 | ); |
| 217 | |
| 218 | } elseif ( ' ' === $char ) { |
| 219 | |
| 220 | $tokens[] = array( |
| 221 | 'type' => 'spacer', |
| 222 | 'value' => $char, |
| 223 | ); |
| 224 | } |
| 225 | } |
| 226 | |
| 227 | return $tokens; |
| 228 | } |
| 229 | |
| 230 | /** |
| 231 | * Render timer HTML. |
| 232 | * |
| 233 | * @param int $seconds Total seconds. |
| 234 | * |
| 235 | * @return void |
| 236 | */ |
| 237 | public static function render( int $seconds ): void { |
| 238 | |
| 239 | $tokens = self::build_tokens( $seconds ); |
| 240 | |
| 241 | echo '<div class="tutor-timer">'; |
| 242 | |
| 243 | foreach ( $tokens as $token ) { |
| 244 | |
| 245 | $type = $token['type']; |
| 246 | $value = $token['value']; |
| 247 | |
| 248 | $class = 'tutor-timer-' . sanitize_html_class( $type ); |
| 249 | |
| 250 | printf( |
| 251 | '<span class="%1$s">%2$s</span>', |
| 252 | esc_attr( $class ), |
| 253 | esc_html( $value ) |
| 254 | ); |
| 255 | } |
| 256 | |
| 257 | echo '</div>'; |
| 258 | } |
| 259 | |
| 260 | /** |
| 261 | * Get remaining seconds between two timestamps. |
| 262 | * |
| 263 | * @param int $future_timestamp Future UNIX timestamp. |
| 264 | * |
| 265 | * @return int |
| 266 | */ |
| 267 | public static function remaining_seconds( int $future_timestamp ): int { |
| 268 | |
| 269 | return max( |
| 270 | 0, |
| 271 | $future_timestamp - time() |
| 272 | ); |
| 273 | } |
| 274 | } |
| 275 |