Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
123 changes: 123 additions & 0 deletions inc/Helpers/Misc.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
<?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( '' ),
];

if ( empty( $file_href ) ) {
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 from file size (e.g., "7ko" → "7" + "ko").
preg_match( '/^([\d.,]+)\s*([a-zA-Z]+)$/', $file_size, $matches );
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Regex fails on non-breaking space thousands separator

Medium Severity

In French WordPress locale, number_format_i18n() uses a non-breaking space (U+00A0 or U+202F) as the thousands separator. For files ≥1000 of any unit (~1 MB), size_format() produces e.g. "1\u00A0000 KB". The regex character class [\d.,]+ doesn't include NBSP, so it captures only "1", and then \s* (which doesn't match NBSP without the u flag in PHP PCRE) causes the overall match to fail. The function falls back to returning the raw size string, completely defeating its accessibility purpose. This is distinct from the (int) cast bug—fixing the cast alone won't help here since the regex itself never matches.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit c949b46. Configure here.

$value = $matches[1] ?? '';
$int_value = (int) $value; // Cast to int for _n() pluralization.
$unit = strtolower( $matches[2] ?? '' );

/* 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( $file_ext, [ 'jpg', 'jpeg', 'png', 'gif', 'webp', 'svg', 'bmp', 'ico' ], true ) ) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Case-sensitive extension check fails for uppercase extensions

Medium Severity

The get_file_icon function uses a strict comparison against a lowercase list of image extensions. Since pathinfo() preserves the original case of the file extension, images with uppercase extensions (e.g., .JPG) are not recognized, resulting in the generic 'file' icon instead of 'file-image'.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit c949b46. Configure here.

$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