From 5bb06bc25b5a5aaa87232808384d3cfeae3e7add Mon Sep 17 00:00:00 2001 From: mricoul Date: Wed, 25 Feb 2026 15:56:55 +0100 Subject: [PATCH 01/13] feat(Misc): adds Misc helper for file information and formatting Introduces a new helper to provide utility functions for handling file-related data. This helper simplifies retrieving comprehensive attachment details and includes a dedicated function (`get_accessible_file_size_label`) to improve accessibility by transforming file size strings into pluralized, human-readable labels. --- functions.php | 2 + inc/Helpers/Misc.php | 137 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 139 insertions(+) create mode 100644 inc/Helpers/Misc.php diff --git a/functions.php b/functions.php index a87370d4..2c533d3c 100644 --- a/functions.php +++ b/functions.php @@ -26,6 +26,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/Datetime.php'; require_once __DIR__ . '/inc/Helpers/Formatting/Escape.php'; diff --git a/inc/Helpers/Misc.php b/inc/Helpers/Misc.php new file mode 100644 index 00000000..3ba5a6f3 --- /dev/null +++ b/inc/Helpers/Misc.php @@ -0,0 +1,137 @@ + '', + 'file_name' => '', + 'path' => '', + 'size' => '', + 'ext' => '', + 'caption' => '', + ]; + + if ( empty( $file_href ) ) { + return $file_infos; + } + + $file_path = get_attached_file( $file_id ); + + if ( empty( $file_path ) ) { + return $file_infos; + } + + $file_ext = get_mime_type( $file_id ); + + 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' => wp_get_attachment_caption( $file_id ), + ]; +} + +/** + * 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 mime type + * + * @param int $file_id + * + * @return string + */ +function get_mime_type( int $file_id ) { + $mime_type = (string) get_post_mime_type( $file_id ); + + if ( empty( $mime_type ) ) { + return ''; + } + + $mime_type = explode( '/', $mime_type ); + + return end( $mime_type ); +} + +/** + * 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 ); + $value = $matches[1] ?? ''; + $int_value = (int) $value; // Cast to int for _n() pluralization. + $unit = strtolower( $matches[2] ?? '' ); + + switch ( $unit ) { + case 'b': + case 'o': + $unit_label = _n( 'byte', 'bytes', $int_value, 'beapi-frontend-framework' ); + break; + case 'kb': + case 'ko': + $unit_label = _n( 'kilobyte', 'kilobytes', $int_value, 'beapi-frontend-framework' ); + break; + case 'mb': + case 'mo': + $unit_label = _n( 'megabyte', 'megabytes', $int_value, 'beapi-frontend-framework' ); + break; + case 'gb': + case 'go': + $unit_label = _n( 'gigabyte', 'gigabytes', $int_value, 'beapi-frontend-framework' ); + break; + case 'tb': + case 'to': + $unit_label = _n( 'terabyte', 'terabytes', $int_value, 'beapi-frontend-framework' ); + break; + default: + return $file_size; + } + + return $value . ' ' . $unit_label; +} From 4a3f51bf047fcd0de6390fd1c4782d4cedc0a3db Mon Sep 17 00:00:00 2001 From: mricoul Date: Wed, 25 Feb 2026 15:58:18 +0100 Subject: [PATCH 02/13] fix(Misc): phpcs issue --- inc/Helpers/Misc.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/inc/Helpers/Misc.php b/inc/Helpers/Misc.php index 3ba5a6f3..116ab290 100644 --- a/inc/Helpers/Misc.php +++ b/inc/Helpers/Misc.php @@ -58,7 +58,7 @@ function get_file_infos( int $file_id ): array { * @return string $file_detail */ function get_file_detail( string $file_name, string $file_ext, string $file_size ): string { - $details = []; + $details = []; if ( ! empty( $file_name ) ) { $details[] = $file_name; From a2189c3f87195f18aed01116ef20c6bc52e76795 Mon Sep 17 00:00:00 2001 From: Milan Ricoul Date: Wed, 25 Feb 2026 16:02:11 +0100 Subject: [PATCH 03/13] Apply suggestion from @sourcery-ai[bot] Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> --- inc/Helpers/Misc.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/inc/Helpers/Misc.php b/inc/Helpers/Misc.php index 116ab290..3d71cc2a 100644 --- a/inc/Helpers/Misc.php +++ b/inc/Helpers/Misc.php @@ -44,7 +44,7 @@ function get_file_infos( int $file_id ): array { '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' => wp_get_attachment_caption( $file_id ), + 'caption' => (string) wp_get_attachment_caption( $file_id ), ]; } From ea5df86d241cb914796c8fc2759c26135a45476e Mon Sep 17 00:00:00 2001 From: mricoul Date: Tue, 3 Mar 2026 10:44:46 +0100 Subject: [PATCH 04/13] feat: adds translator notes for file size units Provides context for translators regarding the `%s` placeholder in pluralized file size unit strings. This ensures more accurate and contextually appropriate translations. --- inc/Helpers/Misc.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/inc/Helpers/Misc.php b/inc/Helpers/Misc.php index 3d71cc2a..b2743fc7 100644 --- a/inc/Helpers/Misc.php +++ b/inc/Helpers/Misc.php @@ -111,22 +111,27 @@ function get_accessible_file_size_label( string $file_size ): string { switch ( $unit ) { case 'b': case 'o': + /* translators: %s: file size */ $unit_label = _n( 'byte', 'bytes', $int_value, 'beapi-frontend-framework' ); break; case 'kb': case 'ko': + /* translators: %s: file size */ $unit_label = _n( 'kilobyte', 'kilobytes', $int_value, 'beapi-frontend-framework' ); break; case 'mb': case 'mo': + /* translators: %s: file size */ $unit_label = _n( 'megabyte', 'megabytes', $int_value, 'beapi-frontend-framework' ); break; case 'gb': case 'go': + /* translators: %s: file size */ $unit_label = _n( 'gigabyte', 'gigabytes', $int_value, 'beapi-frontend-framework' ); break; case 'tb': case 'to': + /* translators: %s: file size */ $unit_label = _n( 'terabyte', 'terabytes', $int_value, 'beapi-frontend-framework' ); break; default: From 77e812532d01a38f86f27843785e3b7375ef39f9 Mon Sep 17 00:00:00 2001 From: mricoul Date: Tue, 3 Mar 2026 10:46:47 +0100 Subject: [PATCH 05/13] refactor(Misc): file size unit labeling Replaces the `switch` statement with a more modern `match` expression in `get_accessible_file_size_label`. This improves code readability and conciseness while maintaining the existing functionality for parsing and presenting file size units. --- inc/Helpers/Misc.php | 40 ++++++++++++---------------------------- 1 file changed, 12 insertions(+), 28 deletions(-) diff --git a/inc/Helpers/Misc.php b/inc/Helpers/Misc.php index b2743fc7..a6fe1c7d 100644 --- a/inc/Helpers/Misc.php +++ b/inc/Helpers/Misc.php @@ -108,34 +108,18 @@ function get_accessible_file_size_label( string $file_size ): string { $int_value = (int) $value; // Cast to int for _n() pluralization. $unit = strtolower( $matches[2] ?? '' ); - switch ( $unit ) { - case 'b': - case 'o': - /* translators: %s: file size */ - $unit_label = _n( 'byte', 'bytes', $int_value, 'beapi-frontend-framework' ); - break; - case 'kb': - case 'ko': - /* translators: %s: file size */ - $unit_label = _n( 'kilobyte', 'kilobytes', $int_value, 'beapi-frontend-framework' ); - break; - case 'mb': - case 'mo': - /* translators: %s: file size */ - $unit_label = _n( 'megabyte', 'megabytes', $int_value, 'beapi-frontend-framework' ); - break; - case 'gb': - case 'go': - /* translators: %s: file size */ - $unit_label = _n( 'gigabyte', 'gigabytes', $int_value, 'beapi-frontend-framework' ); - break; - case 'tb': - case 'to': - /* translators: %s: file size */ - $unit_label = _n( 'terabyte', 'terabytes', $int_value, 'beapi-frontend-framework' ); - break; - default: - return $file_size; + /* 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; From aa781fd7341f4ad2870f67ba4c24feff415b60f0 Mon Sep 17 00:00:00 2001 From: mricoul Date: Tue, 3 Mar 2026 10:48:37 +0100 Subject: [PATCH 06/13] fix(Misc): corrects file extension retrieval Ensures the accurate file extension is obtained by switching from `get_mime_type` to `pathinfo`. The previous method could return a full MIME type, leading to incorrect extension values. --- inc/Helpers/Misc.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/inc/Helpers/Misc.php b/inc/Helpers/Misc.php index a6fe1c7d..fea014f6 100644 --- a/inc/Helpers/Misc.php +++ b/inc/Helpers/Misc.php @@ -30,7 +30,7 @@ function get_file_infos( int $file_id ): array { return $file_infos; } - $file_ext = get_mime_type( $file_id ); + $file_ext = pathinfo( $file_path, PATHINFO_EXTENSION ); if ( empty( $file_ext ) ) { return $file_infos; From fa6a384a7c170659febf909fd816fb606ba7ffbd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ce=CC=81dric=20Andrietti?= Date: Fri, 3 Apr 2026 15:45:57 +0200 Subject: [PATCH 07/13] remove unused function --- inc/Helpers/Misc.php | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/inc/Helpers/Misc.php b/inc/Helpers/Misc.php index fea014f6..2d58f9c3 100644 --- a/inc/Helpers/Misc.php +++ b/inc/Helpers/Misc.php @@ -75,25 +75,6 @@ function get_file_detail( string $file_name, string $file_ext, string $file_size return implode( ' – ', $details ); } -/** - * Get mime type - * - * @param int $file_id - * - * @return string - */ -function get_mime_type( int $file_id ) { - $mime_type = (string) get_post_mime_type( $file_id ); - - if ( empty( $mime_type ) ) { - return ''; - } - - $mime_type = explode( '/', $mime_type ); - - return end( $mime_type ); -} - /** * Get accessible file size label * From e0f22ddfa2d4d3b1a18ee31ba908fd9d5bbcfe89 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ce=CC=81dric=20Andrietti?= Date: Fri, 3 Apr 2026 15:48:31 +0200 Subject: [PATCH 08/13] add icons files --- inc/Helpers/Misc.php | 17 +++++++++++++++++ src/img/icons/sprite/file-image.svg | 1 + src/img/icons/sprite/file.svg | 1 + 3 files changed, 19 insertions(+) create mode 100644 src/img/icons/sprite/file-image.svg create mode 100644 src/img/icons/sprite/file.svg diff --git a/inc/Helpers/Misc.php b/inc/Helpers/Misc.php index 2d58f9c3..3bf68d28 100644 --- a/inc/Helpers/Misc.php +++ b/inc/Helpers/Misc.php @@ -45,6 +45,7 @@ function get_file_infos( int $file_id ): array { '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 ), ]; } @@ -105,3 +106,19 @@ function get_accessible_file_size_label( string $file_size ): string { 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 ) ) { + $file_icon = 'file-image'; + } + + return $file_icon; +} diff --git a/src/img/icons/sprite/file-image.svg b/src/img/icons/sprite/file-image.svg new file mode 100644 index 00000000..973318f4 --- /dev/null +++ b/src/img/icons/sprite/file-image.svg @@ -0,0 +1 @@ + diff --git a/src/img/icons/sprite/file.svg b/src/img/icons/sprite/file.svg new file mode 100644 index 00000000..b9469573 --- /dev/null +++ b/src/img/icons/sprite/file.svg @@ -0,0 +1 @@ + From e0f493b93b7b61638899a847f016e3fc2916475a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ce=CC=81dric=20Andrietti?= Date: Wed, 8 Apr 2026 13:47:54 +0200 Subject: [PATCH 09/13] Fix bugbot - Mismatched array keys between error and success paths --- inc/Helpers/Misc.php | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/inc/Helpers/Misc.php b/inc/Helpers/Misc.php index 3bf68d28..a3545c24 100644 --- a/inc/Helpers/Misc.php +++ b/inc/Helpers/Misc.php @@ -12,12 +12,12 @@ function get_file_infos( int $file_id ): array { $file_href = wp_get_attachment_url( $file_id ); $file_infos = [ - 'href' => '', - 'file_name' => '', - 'path' => '', - 'size' => '', - 'ext' => '', - 'caption' => '', + 'file_name' => '', + 'details' => '', + 'details_accessible' => '', + 'href' => '', + 'caption' => '', + 'icon' => get_file_icon( '' ), ]; if ( empty( $file_href ) ) { @@ -113,7 +113,6 @@ function get_accessible_file_size_label( string $file_size ): string { * @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 ) ) { From 7ec2a25d96e465ca40fe497fefd20d156b402a25 Mon Sep 17 00:00:00 2001 From: Milan Ricoul Date: Fri, 24 Apr 2026 15:32:58 +0200 Subject: [PATCH 10/13] fix: resolve file-image icon for uppercase extensions and AVIF Compare extensions case-insensitively via strtolower() and include avif in the image list. --- inc/Helpers/Misc.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/inc/Helpers/Misc.php b/inc/Helpers/Misc.php index a3545c24..ce451e2a 100644 --- a/inc/Helpers/Misc.php +++ b/inc/Helpers/Misc.php @@ -115,7 +115,7 @@ function get_accessible_file_size_label( string $file_size ): 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 ) ) { + if ( in_array( strtolower( $file_ext ), [ 'jpg', 'jpeg', 'png', 'gif', 'webp', 'avif', 'svg', 'bmp', 'ico' ], true ) ) { $file_icon = 'file-image'; } From b51c2ccfc0d87733af12b84cc3dbba0f97cd69f2 Mon Sep 17 00:00:00 2001 From: Milan Ricoul Date: Fri, 24 Apr 2026 15:35:49 +0200 Subject: [PATCH 11/13] fix(Misc): parse i18n file sizes with Unicode thousands separators Handle size_format() output that uses NBSP/NNBSP and other Zs chars from number_format_i18n() (e.g. fr_FR). Use UTF-8 regex with a lazy value group, early return on no match, and strip group separators for _n() pluralization. --- inc/Helpers/Misc.php | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/inc/Helpers/Misc.php b/inc/Helpers/Misc.php index ce451e2a..8b14bc55 100644 --- a/inc/Helpers/Misc.php +++ b/inc/Helpers/Misc.php @@ -84,11 +84,16 @@ function get_file_detail( string $file_name, string $file_ext, 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 ); - $value = $matches[1] ?? ''; - $int_value = (int) $value; // Cast to int for _n() pluralization. - $unit = strtolower( $matches[2] ?? '' ); + // 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 ); /* translators: file size units (byte, kilobyte, megabyte, etc.) */ $unit_label = match ( $unit ) { From ea04c43cdff4820d6ffbca491eb62d0a9629f891 Mon Sep 17 00:00:00 2001 From: Milan Ricoul Date: Wed, 4 Nov 2026 17:15:37 +0100 Subject: [PATCH 12/13] perf(Misc): cache file infos and use attachment filesize meta --- inc/Helpers/Misc.php | 136 +++++++++++++++++++++++++++++++++---------- 1 file changed, 106 insertions(+), 30 deletions(-) diff --git a/inc/Helpers/Misc.php b/inc/Helpers/Misc.php index 8b14bc55..72d9b5c2 100644 --- a/inc/Helpers/Misc.php +++ b/inc/Helpers/Misc.php @@ -5,12 +5,25 @@ /** * Get file infos * - * @param int $file_id + * @param int $file_id Attachment ID. * - * @return array $file_infos + * @return array{ + * file_name: string, + * details: string, + * details_accessible: string, + * href: string, + * caption: string, + * icon: string + * } */ function get_file_infos( int $file_id ): array { - $file_href = wp_get_attachment_url( $file_id ); + $cache_group = 'beapi_file_infos'; + $cached = wp_cache_get( $file_id, $cache_group ); + + if ( false !== $cached && is_array( $cached ) ) { + return $cached; + } + $file_infos = [ 'file_name' => '', 'details' => '', @@ -20,41 +33,86 @@ function get_file_infos( int $file_id ): array { 'icon' => get_file_icon( '' ), ]; + $file_href = wp_get_attachment_url( $file_id ); + if ( empty( $file_href ) ) { + wp_cache_set( $file_id, $file_infos, $cache_group ); + return $file_infos; } $file_path = get_attached_file( $file_id ); if ( empty( $file_path ) ) { + wp_cache_set( $file_id, $file_infos, $cache_group ); + return $file_infos; } $file_ext = pathinfo( $file_path, PATHINFO_EXTENSION ); if ( empty( $file_ext ) ) { + wp_cache_set( $file_id, $file_infos, $cache_group ); + return $file_infos; } - $file_size = (string) size_format( wp_filesize( $file_path ) ); - $file_name = (string) ( get_the_title( $file_id ) ?? '' ); + $file_bytes = get_attachment_filesize( $file_id, $file_path ); + $file_size = $file_bytes > 0 ? (string) size_format( $file_bytes ) : ''; + $accessible_file_size = $file_bytes > 0 ? get_accessible_file_size_label( $file_bytes ) : ''; + $file_name = (string) ( get_the_title( $file_id ) ?? '' ); - return [ + $file_infos = [ '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 ) ), + 'details_accessible' => get_file_detail( $file_name, $file_ext, $accessible_file_size ), 'href' => $file_href, 'caption' => (string) wp_get_attachment_caption( $file_id ), 'icon' => get_file_icon( $file_ext ), ]; + + wp_cache_set( $file_id, $file_infos, $cache_group ); + + return $file_infos; +} + +/** + * Resolve attachment filesize in bytes from metadata, with wp_filesize() fallback and meta backfill. + * + * @param int $file_id Attachment ID. + * @param string $file_path Absolute path to the attached file. + * + * @return int Filesize in bytes, or 0 when unavailable. + */ +function get_attachment_filesize( int $file_id, string $file_path ): int { + $metadata = wp_get_attachment_metadata( $file_id ); + + if ( is_array( $metadata ) && ! empty( $metadata['filesize'] ) ) { + return (int) $metadata['filesize']; + } + + $filesize = wp_filesize( $file_path ); + + if ( $filesize <= 0 ) { + return 0; + } + + if ( ! is_array( $metadata ) ) { + $metadata = []; + } + + $metadata['filesize'] = $filesize; + wp_update_attachment_metadata( $file_id, $metadata ); + + return $filesize; } /** * Get file details * - * @param string $file_name - * @param string $file_ext - * @param string $file_size + * @param string $file_name File display name. + * @param string $file_ext File extension. + * @param string $file_size Formatted file size label. * * @return string $file_detail */ @@ -77,43 +135,61 @@ function get_file_detail( string $file_name, string $file_ext, string $file_size } /** - * Get accessible file size label + * Get accessible file size label from raw bytes (screen-reader friendly, pluralized units). + * + * Mirrors size_format() unit thresholds without parsing its localized output. * - * @param string $file_size + * @param int $bytes File size in bytes. * * @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; +function get_accessible_file_size_label( int $bytes ): string { + if ( $bytes <= 0 ) { + return ''; + } + + $quant = [ + 'tb' => TB_IN_BYTES, + 'gb' => GB_IN_BYTES, + 'mb' => MB_IN_BYTES, + 'kb' => KB_IN_BYTES, + 'b' => 1, + ]; + + $unit = 'b'; + $mag = 1; + + foreach ( $quant as $unit_key => $unit_mag ) { + if ( (float) $bytes >= $unit_mag ) { + $unit = $unit_key; + $mag = $unit_mag; + break; + } } - $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 ); + $value = $bytes / $mag; + $int_value = (int) round( $value ); + $formatted = number_format_i18n( $value, 0 ); /* 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, + 'b' => _n( 'byte', 'bytes', $int_value, 'beapi-frontend-framework' ), + 'kb' => _n( 'kilobyte', 'kilobytes', $int_value, 'beapi-frontend-framework' ), + 'mb' => _n( 'megabyte', 'megabytes', $int_value, 'beapi-frontend-framework' ), + 'gb' => _n( 'gigabyte', 'gigabytes', $int_value, 'beapi-frontend-framework' ), + 'tb' => _n( 'terabyte', 'terabytes', $int_value, 'beapi-frontend-framework' ), + default => null, }; if ( null === $unit_label ) { - return $file_size; + return ''; } - return $value . ' ' . $unit_label; + return $formatted . ' ' . $unit_label; } /** - * @param string $file_ext + * @param string $file_ext File extension. * * @return string */ From 1126aece13bacb209d7d1225a3e207371a628775 Mon Sep 17 00:00:00 2001 From: Milan Ricoul Date: Wed, 4 Nov 2026 17:26:04 +0100 Subject: [PATCH 13/13] fix: File info cache never invalidates --- inc/Helpers/Misc.php | 44 ++++++++++++++++++++++++++++++++++++++------ 1 file changed, 38 insertions(+), 6 deletions(-) diff --git a/inc/Helpers/Misc.php b/inc/Helpers/Misc.php index 72d9b5c2..54dfeef9 100644 --- a/inc/Helpers/Misc.php +++ b/inc/Helpers/Misc.php @@ -2,6 +2,9 @@ namespace BEA\Theme\Framework\Helpers\Misc; +// Object cache group for get_file_infos(). +const FILE_INFOS_CACHE_GROUP = 'beapi_file_infos'; + /** * Get file infos * @@ -17,8 +20,7 @@ * } */ function get_file_infos( int $file_id ): array { - $cache_group = 'beapi_file_infos'; - $cached = wp_cache_get( $file_id, $cache_group ); + $cached = wp_cache_get( $file_id, FILE_INFOS_CACHE_GROUP ); if ( false !== $cached && is_array( $cached ) ) { return $cached; @@ -36,7 +38,7 @@ function get_file_infos( int $file_id ): array { $file_href = wp_get_attachment_url( $file_id ); if ( empty( $file_href ) ) { - wp_cache_set( $file_id, $file_infos, $cache_group ); + wp_cache_set( $file_id, $file_infos, FILE_INFOS_CACHE_GROUP ); return $file_infos; } @@ -44,7 +46,7 @@ function get_file_infos( int $file_id ): array { $file_path = get_attached_file( $file_id ); if ( empty( $file_path ) ) { - wp_cache_set( $file_id, $file_infos, $cache_group ); + wp_cache_set( $file_id, $file_infos, FILE_INFOS_CACHE_GROUP ); return $file_infos; } @@ -52,7 +54,7 @@ function get_file_infos( int $file_id ): array { $file_ext = pathinfo( $file_path, PATHINFO_EXTENSION ); if ( empty( $file_ext ) ) { - wp_cache_set( $file_id, $file_infos, $cache_group ); + wp_cache_set( $file_id, $file_infos, FILE_INFOS_CACHE_GROUP ); return $file_infos; } @@ -71,11 +73,41 @@ function get_file_infos( int $file_id ): array { 'icon' => get_file_icon( $file_ext ), ]; - wp_cache_set( $file_id, $file_infos, $cache_group ); + wp_cache_set( $file_id, $file_infos, FILE_INFOS_CACHE_GROUP ); return $file_infos; } +/** + * Delete cached file infos for an attachment. + * + * @param int $file_id Attachment ID. + */ +function delete_file_infos_cache( int $file_id ): void { + wp_cache_delete( $file_id, FILE_INFOS_CACHE_GROUP ); +} + +/** + * Invalidate file infos cache when attached file path or metadata (incl. filesize) change. + * + * @param mixed $meta_id Meta ID, or list of meta IDs for deleted_post_meta. + * @param int $object_id Object ID. + * @param string $meta_key Meta key. + */ +function delete_file_infos_cache_on_meta_change( $meta_id, $object_id, string $meta_key ): void { + if ( ! in_array( $meta_key, [ '_wp_attachment_metadata', '_wp_attached_file' ], true ) ) { + return; + } + + delete_file_infos_cache( (int) $object_id ); +} + +add_action( 'edit_attachment', __NAMESPACE__ . '\\delete_file_infos_cache' ); +add_action( 'delete_attachment', __NAMESPACE__ . '\\delete_file_infos_cache' ); +add_action( 'added_post_meta', __NAMESPACE__ . '\\delete_file_infos_cache_on_meta_change', 10, 3 ); +add_action( 'updated_post_meta', __NAMESPACE__ . '\\delete_file_infos_cache_on_meta_change', 10, 3 ); +add_action( 'deleted_post_meta', __NAMESPACE__ . '\\delete_file_infos_cache_on_meta_change', 10, 3 ); + /** * Resolve attachment filesize in bytes from metadata, with wp_filesize() fallback and meta backfill. *