WAIT: Bricks ACF Icon Picker results in self-induced HTTP timeout on SVG inlining

File: includes/integrations/dynamic-data/providers/provider-acf.php
Method: Provider_Acf::process_icon_picker_field()
Lines: 973, 1025, 1051

Problem

The method inlines SVG content by passing a URL to file_get_contents():

// Line 973 (return_format = string, value is URL)
$svg = file_get_contents( $value, false, $context );

// Line 1025 (return_format = array, type = media_library, mime = image/svg+xml)
$svg = file_get_contents( wp_get_attachment_url( $attachment_id ), false, $context );

// Line 1051 (return_format = array, type = url, .svg extension)
$svg = file_get_contents( $value['value'], false, $context );

When the URL is same-origin (typical: an icon from this site’s Media Library), PHP opens an HTTPS socket back to its own FPM pool. Under any pool that isn’t oversized, this deadlocks until max_execution_time fires. The result: fatal error, blank/timed-out page, nginx logs upstream timed out. Inside a loop with N icons this multiplies per pageload and saturates the pool.

Reproducible with a Media Library SVG used as an Icon Picker.

Fix

Read the file from disk instead of fetching its URL over HTTP.

Line 1025 — preferred fix (we already have the attachment ID):

$path = get_attached_file( $attachment_id );
$svg  = is_readable( $path ) ? file_get_contents( $path ) : false;

Lines 973 and 1051 — same-origin URLs:

$home = home_url( '/' );
if ( strpos( $url, $home ) === 0 ) {
    $path = wp_normalize_path( ABSPATH . substr( $url, strlen( $home ) ) );
    $svg  = is_readable( $path ) ? file_get_contents( $path ) : false;
} else {
    $svg = wp_safe_remote_retrieve_body( wp_safe_remote_get( $url ) );
}

This also removes the need for the SSL verify_peer=false context.

Severity

High for any site using ACF Icon Picker values referenced on every page. Symptom is sitewide unresponsiveness, not just the affected element.

Note: this bug isn’t just related to v2-3-4. Unfortunately, I can’t add any other version tags to the post.

Hey @wbshr, can you record a video how to replicate it? Because I can’t do it locally.

Thank you,
Matej

1 Like

Hey @Matej

Thanks for the prompt answer. Unfortunately, a screencast wouldn’t help here because the bug is entirely server-side: PHP makes an HTTP request back to its own server. The browser only ever sees the outer page request and the loopback never reaches its network panel.

What’s actually happening

In Provider_Acf::process_icon_picker_field() (provider-acf.php, line 973):

$svg = file_get_contents( $value, false, $context );

When the ACF Icon Picker is set to a Media Library SVG, $value is a same-origin URL. PHP opens an HTTPS connection to its own nginx, which dispatches a second PHP-FPM worker to serve the file. That’s wasteful by design. The file is already on disk. There is no reason to fetch it over HTTP from the same server.

The nginx access log shows it cleanly. From one page load:

127.0.0.1 - - [15/May/2026:12:21:27 +0200] "GET /wp-content/uploads/2026/05/bricks-logo.svg HTTP/1.1" 200 19794 "-" "-"
127.0.0.1 - - [15/May/2026:12:21:27 +0200] "GET /acf-icon-picker/ HTTP/2.0" 200 20792 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36"
127.0.0.1 - - [15/May/2026:12:21:27 +0200] "GET /wp-content/themes/bricks/assets/css/frontend-layer.min.css?ver=1778836808 HTTP/2.0" 304 0 "https://bricks.test/acf-icon-picker/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36"
127.0.0.1 - - [15/May/2026:12:21:27 +0200] "GET /wp-content/themes/bricks/assets/css/admin.min.css?ver=1778836808 HTTP/2.0" 304 0 "https://bricks.test/acf-icon-picker/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36"
127.0.0.1 - - [15/May/2026:12:21:27 +0200] "GET /wp-content/themes/bricks/assets/js/bricks.min.js?ver=1778836809 HTTP/2.0" 304 0 "https://bricks.test/acf-icon-picker/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36"
127.0.0.1 - - [15/May/2026:12:21:28 +0200] "GET /favicon.ico HTTP/2.0" 302 0 "https://bricks.test/acf-icon-picker/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36"

The SVG GET is logged before the page request, because nginx logs after response completion. PHP finished fetching its own SVG over HTTPS before the page itself finished rendering.

Three signatures on that first line make it unmistakably PHP, not a browser:

  • HTTP/1.1 (the browser request right below is HTTP/2.0)
  • empty Referer (“-”)
  • empty User-Agent (“-”)

Attached:

  • screenshots (Bricks element, ACF field)
  • acf-export.json

Thanks again for looking!

[
    {
        "key": "group_6a06e5867b451",
        "title": "Custom Fields",
        "fields": [
            {
                "key": "field_6a06f2a531685",
                "label": "Icon Picker",
                "name": "icon_picker",
                "aria-label": "",
                "type": "icon_picker",
                "instructions": "",
                "required": 0,
                "conditional_logic": 0,
                "wrapper": {
                    "width": "",
                    "class": "",
                    "id": ""
                },
                "tabs": [
                    "media_library",
                    "url"
                ],
                "return_format": "string",
                "allow_in_bindings": 0,
                "library": "all",
                "default_value": {
                    "type": null,
                    "value": null
                }
            },
            {
                "key": "field_6a06eb3a39a0d",
                "label": "Repeater",
                "name": "repeater",
                "aria-label": "",
                "type": "repeater",
                "instructions": "",
                "required": 0,
                "conditional_logic": 0,
                "wrapper": {
                    "width": "",
                    "class": "",
                    "id": ""
                },
                "layout": "block",
                "pagination": 0,
                "min": 0,
                "max": 0,
                "collapsed": "",
                "button_label": "Add Row",
                "rows_per_page": 20,
                "sub_fields": [
                    {
                        "key": "field_6a06eb4239a0e",
                        "label": "Icon Picker",
                        "name": "icon_picker_repeater",
                        "aria-label": "",
                        "type": "icon_picker",
                        "instructions": "",
                        "required": 0,
                        "conditional_logic": 0,
                        "wrapper": {
                            "width": "",
                            "class": "",
                            "id": ""
                        },
                        "tabs": [
                            "media_library",
                            "url"
                        ],
                        "return_format": "string",
                        "allow_in_bindings": 0,
                        "library": "all",
                        "default_value": {
                            "type": null,
                            "value": null
                        },
                        "parent_repeater": "field_6a06eb3a39a0d"
                    }
                ]
            }
        ],
        "location": [
            [
                {
                    "param": "post_type",
                    "operator": "==",
                    "value": "page"
                }
            ]
        ],
        "menu_order": 0,
        "position": "normal",
        "style": "default",
        "label_placement": "top",
        "instruction_placement": "label",
        "hide_on_screen": "",
        "active": true,
        "description": "",
        "show_in_rest": 0,
        "display_title": "",
        "allow_ai_access": false,
        "ai_description": ""
    }
]

Follow-up with corrections and the actual patch proposal.

The fix already exists in Bricks. Element_Svg::render() does it correctly three times, including the docblock on Helpers::file_get_contents() that explicitly names .svg:

$svg_path = get_attached_file( $attachment_id );
$svg      = $svg_path ? \Bricks\Helpers::file_get_contents( $svg_path ) : false;

Line 1056 takes this literally. Lines 1004 and 1083 need URL-to-ID resolution first:

$attachment_id = attachment_url_to_postid( $value );
if ( $attachment_id ) {
    $svg_path = get_attached_file( $attachment_id );
    $svg      = $svg_path ? \Bricks\Helpers::file_get_contents( $svg_path ) : false;
} else {
    // External URL: mirror Element_Svg::get_svg_from_url() (status 200 + is_valid_svg checks).
}

Removes the verify_peer => false stream context as a side effect.

Gotcha when applying: \Bricks\Helpers::file_get_contents() returns '' on failure, not false. The existing if ( $svg !== false ) guards must change to if ( ! empty( $svg ) ), otherwise missing files silently render an empty SVG.

Any updates on the bug from your end, @Matej?

Hey @wbshr,

Sorry it took us so long to get back to this.

Thanks for the detailed follow-up and patch suggestion. I was able to confirm the self-origin SVG request in the NGINX access log, and we’ve now added this to our internal tracker.

The fix follows the approach you suggested: Media Library SVGs are now read from the attachment file path instead of being fetched through their own URL, and URL-based SVG values first try to resolve to a Media Library attachment before falling back to a remote request for external SVGs.

If you’d like to test the fix on your setup once I fully tested it, please email help@bricksbuilder.io with a link to this forum topic, using the same email address you used for your Bricks purchase. Alternatively, you can DM me the email address connected to your Bricks purchase. This way, we could make sure that it works correctly :slight_smile:

Thanks again for the clear report and the implementation notes.

Matej