= 1_073_741_824 ) { return number_format( $bytes / 1_073_741_824, 2 ) . ' GB'; } if ( $bytes >= 1_048_576 ) { return number_format( $bytes / 1_048_576, 2 ) . ' MB'; } if ( $bytes >= 1_024 ) { return number_format( $bytes / 1_024, 2 ) . ' KB'; } return $bytes . ' B'; } /** Formats a duration in seconds, e.g. "2m 34s". */ public static function duration( int $seconds ): string { if ( $seconds < 60 ) { return "{$seconds}s"; } $minutes = intdiv( $seconds, 60 ); $secs = $seconds % 60; if ( $minutes < 60 ) { return "{$minutes}m {$secs}s"; } $hours = intdiv( $minutes, 60 ); $minutes = $minutes % 60; return "{$hours}h {$minutes}m {$secs}s"; } /** Formats progress as a percentage with one decimal, e.g. "74.3%". */ public static function percent( int $processed, int $total ): string { if ( $total <= 0 ) { return '0%'; } return number_format( min( 100, ( $processed / $total ) * 100 ), 1 ) . '%'; } /** * The first $bytes bytes of a UTF-8 string, never ending mid-character. * * mbstring is optional and WordPress does not polyfill mb_strcut(), so the * fallback drops a trailing partial sequence by hand. */ public static function cut_utf8( string $value, int $bytes ): string { if ( function_exists( 'mb_strcut' ) ) { return mb_strcut( $value, 0, $bytes, 'UTF-8' ); } $cut = substr( $value, 0, max( 0, $bytes ) ); if ( 1 === preg_match( '/[\xC0-\xF7][\x80-\xBF]*$/', $cut, $tail ) ) { $lead = ord( $tail[0][0] ); $need = $lead >= 0xF0 ? 4 : ( $lead >= 0xE0 ? 3 : 2 ); if ( strlen( $tail[0] ) < $need ) { $cut = substr( $cut, 0, -strlen( $tail[0] ) ); } } return $cut; } }