Skip to content
Open
2 changes: 2 additions & 0 deletions functions.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ function () {
\BEA\Theme\Framework\Framework::get_container()->boot_services();
}
);

require_once __DIR__ . '/inc/Helpers/Misc.php';
require_once __DIR__ . '/inc/Helpers/Svg.php';
require_once __DIR__ . '/inc/Helpers/Formatting/Escape.php';
require_once __DIR__ . '/inc/Helpers/Formatting/Image.php';
Expand Down
128 changes: 128 additions & 0 deletions inc/Helpers/Misc.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
<?php

namespace BEA\Theme\Framework\Helpers\Misc;

/**
* Get file infos
*
* @param int $file_id
*
* @return array $file_infos
*/
function get_file_infos( int $file_id ): array {
$file_href = wp_get_attachment_url( $file_id );
$file_infos = [
'file_name' => '',
'details' => '',
'details_accessible' => '',
'href' => '',
'caption' => '',
'icon' => get_file_icon( '' ),
];
Comment thread
cursor[bot] marked this conversation as resolved.

if ( empty( $file_href ) ) {
Comment thread
sourcery-ai[bot] marked this conversation as resolved.
return $file_infos;
}

$file_path = get_attached_file( $file_id );

if ( empty( $file_path ) ) {
return $file_infos;
}

$file_ext = pathinfo( $file_path, PATHINFO_EXTENSION );

if ( empty( $file_ext ) ) {
return $file_infos;
}

$file_size = (string) size_format( wp_filesize( $file_path ) );
$file_name = (string) ( get_the_title( $file_id ) ?? '' );

return [
'file_name' => $file_name,
'details' => get_file_detail( $file_name, $file_ext, $file_size ),
'details_accessible' => get_file_detail( $file_name, $file_ext, get_accessible_file_size_label( $file_size ) ),
'href' => $file_href,
'caption' => (string) wp_get_attachment_caption( $file_id ),
'icon' => get_file_icon( $file_ext ),
];
}

/**
* Get file details
*
* @param string $file_name
* @param string $file_ext
* @param string $file_size
*
* @return string $file_detail
*/
function get_file_detail( string $file_name, string $file_ext, string $file_size ): string {
$details = [];

if ( ! empty( $file_name ) ) {
$details[] = $file_name;
}

if ( ! empty( $file_ext ) ) {
$details[] = strtoupper( $file_ext );
}

if ( ! empty( $file_size ) ) {
$details[] = $file_size;
}

return implode( ' – ', $details );
}

/**
* Get accessible file size label
*
* @param string $file_size
*
* @return string
*/
function get_accessible_file_size_label( string $file_size ): string {
// Extract value and unit (e.g. "7ko" or "1\u{00A0}000 KB" from i18n thousands separators).
// UTF-8 mode: allow NBSP/NNBSP inside the value; a non-possessive +? so the last \s* is the gap before the unit, not the thousands separator.
if ( 1 !== preg_match( '/^([\d\.,\p{Zs}]+?)\s*+([a-zA-Z]+)$/u', $file_size, $matches ) ) {
return $file_size;
}

$value = $matches[1] ?? '';
$unit = strtolower( $matches[2] ?? '' );
// Strip group separators (ASCII space, NBSP, NNBSP) for _n() plural; (int) leaves decimals as floor (e.g. 1.5 -> 1).
$int_value = (int) str_replace( [ ' ', "\u{00A0}", "\u{202F}" ], '', $value );
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thousands separator stripping misses commas and dots

High Severity

The str_replace on this line strips only space-based group separators (ASCII space, NBSP, NNBSP) before casting to (int), but WordPress's number_format_i18n uses commas in English and dots in German as thousands separators. For a file around ~1 MB, size_format returns "1,023 KB" in English. Since the comma isn't stripped, (int) "1,023" evaluates to 1, causing _n to return the singular form — producing "1,023 kilobyte" instead of "1,023 kilobytes". This affects the current get_file_infos caller for any file between 1,000 and 1,023 of any unit.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 7a062ea. Configure here.


/* translators: file size units (byte, kilobyte, megabyte, etc.) */
$unit_label = match ( $unit ) {
'b', 'o' => _n( 'byte', 'bytes', $int_value, 'beapi-frontend-framework' ),
'kb', 'ko' => _n( 'kilobyte', 'kilobytes', $int_value, 'beapi-frontend-framework' ),
'mb', 'mo' => _n( 'megabyte', 'megabytes', $int_value, 'beapi-frontend-framework' ),
'gb', 'go' => _n( 'gigabyte', 'gigabytes', $int_value, 'beapi-frontend-framework' ),
'tb', 'to' => _n( 'terabyte', 'terabytes', $int_value, 'beapi-frontend-framework' ),
default => null,
};

if ( null === $unit_label ) {
return $file_size;
}

return $value . ' ' . $unit_label;
}

/**
* @param string $file_ext
*
* @return string
*/
function get_file_icon( string $file_ext ): string {
$file_icon = 'file';

if ( in_array( strtolower( $file_ext ), [ 'jpg', 'jpeg', 'png', 'gif', 'webp', 'avif', 'svg', 'bmp', 'ico' ], true ) ) {
$file_icon = 'file-image';
}

return $file_icon;
}
1 change: 1 addition & 0 deletions src/img/icons/sprite/file-image.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions src/img/icons/sprite/file.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading