Commit 471cc0a1599 for woocommerce
commit 471cc0a15999e821002400d5a4a32138b7167846
Author: Daniel Mallory <daniel.mallory@automattic.com>
Date: Mon Nov 9 17:03:16 2026 +0000
Fix an infinite loop with Real Media Library on attachment GUIDs (#70782)
* fix(downloads): Fix an infinite loop with plugins that read GUIDs in meta filters
Downloadable file attachment hooks, including get_the_guid, read the
_wp_attached_file meta. Real Media Library filters that meta and reads
the GUID to detect shortcuts, so each filter triggered the other until
PHP ran out of memory. Atomic sites on 11.3 RC-1 with Real Media
Library hit 512MB memory fatals on pages with media.
Skip the downloadable file check while one is already running. The
nested GUID read then returns the stored GUID, so shortcut detection
still works and shortcuts to downloadable files stay hidden.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* test(downloads): Remove an assertion an existing test already covers
The downloadable file GUID check repeats test_comments_and_guid. The
shortcut assertion already shows that protection holds with the
Real Media Library-style filter active.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* refactor(downloads): Rename the downloadable file check flag to in_progress
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* refactor(downloads): Apply review feedback on the recursion fix
Drop the regression test: it only guarded against one way an extension
can hook the same filters, and a regression killed the PHPUnit process
instead of failing. Clarify the in_progress docblock, and use a review
comment for the changelog since the recursion never shipped in a
release.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
diff --git a/plugins/woocommerce/changelog/fix-downloadable-file-attachments-recursion b/plugins/woocommerce/changelog/fix-downloadable-file-attachments-recursion
new file mode 100644
index 00000000000..fa96c5d846d
--- /dev/null
+++ b/plugins/woocommerce/changelog/fix-downloadable-file-attachments-recursion
@@ -0,0 +1,3 @@
+Significance: patch
+Type: fix
+Comment: Fix unreleased recursion between downloadable file hiding and Real Media Library (#70198).
diff --git a/plugins/woocommerce/src/Internal/ProductDownloads/DownloadableFileAttachments.php b/plugins/woocommerce/src/Internal/ProductDownloads/DownloadableFileAttachments.php
index dd23cb2257d..5232eeeaf42 100644
--- a/plugins/woocommerce/src/Internal/ProductDownloads/DownloadableFileAttachments.php
+++ b/plugins/woocommerce/src/Internal/ProductDownloads/DownloadableFileAttachments.php
@@ -37,6 +37,14 @@ class DownloadableFileAttachments implements RegisterHooksInterface {
*/
private const DIRECTORY = 'woocommerce_uploads/';
+ /**
+ * True while is_downloadable_file() reads an attachment's file path. Checks that the read triggers again return
+ * false instead of recursing, so every attachment counts as visible during that read.
+ *
+ * @var bool
+ */
+ private bool $in_progress = false;
+
/**
* Register hooks and filters.
*/
@@ -257,12 +265,18 @@ class DownloadableFileAttachments implements RegisterHooksInterface {
*/
private function is_downloadable_file( int $attachment_id ): bool {
// Checking the post type first skips a meta lookup for the posts that comment and GUID hooks run on.
- if ( $attachment_id <= 0 || 'attachment' !== get_post_type( $attachment_id ) ) {
+ if ( $this->in_progress || $attachment_id <= 0 || 'attachment' !== get_post_type( $attachment_id ) ) {
return false;
}
- // Case-insensitive to match the SQL LIKE used for REST API collections.
- return 0 === stripos( (string) get_post_meta( $attachment_id, '_wp_attached_file', true ), self::DIRECTORY );
+ // Plugins can filter the meta read below with a function that fires these hooks again, such as get_the_guid().
+ $this->in_progress = true;
+ try {
+ // Case-insensitive to match the SQL LIKE used for REST API collections.
+ return 0 === stripos( (string) get_post_meta( $attachment_id, '_wp_attached_file', true ), self::DIRECTORY );
+ } finally {
+ $this->in_progress = false;
+ }
}
/**