Make WordPress Core

Opened 4 months ago

Last modified 13 days ago

#65044 reviewing defect (bug)

get_post_custom_values() missing array check, inconsistent with get_post_custom_keys()

Reported by: saratheonline Owned by: westonruter
Priority: normal Milestone: Future Release
Component: Posts, Post Types Version: 7.0
Severity: normal Keywords: has-patch has-test-info has-unit-tests early
Cc: Focuses:

Description

Explain that get_post_custom() can return false or "", and that the sibling function

get_post_custom_keys() guards against this but get_post_custom_values() does not, causing a PHP 8+ warning.

Attachments (2)

65044_ajax_test.png (22.7 KB ) - added by liaison 4 months ago.
65044-github-ci-238pass.png (19.8 KB ) - added by liaison 4 months ago.

Download all attachments as: .zip

Change History (21)

#1 @saratheonline
4 months ago

  • Keywords has-patch needs-testing added
Version 0, edited 4 months ago by saratheonline (next)

This ticket was mentioned in PR #11496 on WordPress/wordpress-develop by @saratheonline.


4 months ago
#2

Trac ticket: Core-65044

Posts, Post Types: Fix missing array check in get_post_custom_values().

get_post_custom() can return false for an invalid post ID or an empty
string for a non-existing post. get_post_custom_values() accessed the
result as an array without first verifying it is one, triggering a PHP
warning on PHP 8+.


Adds an is_array() guard consistent with the sibling function
get_post_custom_keys(), which already handles this case correctly.

Fixes #65044

Posts, Post Types: Fix missing array check in get_post_custom_values().

get_post_custom() can return false for an invalid post ID or an empty
string for a non-existing post. get_post_custom_values() accessed the
result as an array without first verifying it is one, triggering a PHP
warning on PHP 8+.


Adds an is_array() guard consistent with the sibling function
get_post_custom_keys(), which already handles this case correctly.

## Use of AI Tools

@westonruter commented on PR #11496:


4 months ago
#3

Could you add a test case for this change that the new code is covered?

#4 @westonruter
4 months ago

  • Keywords needs-unit-tests added
  • Milestone Awaiting Review7.1

#5 @liaison
4 months ago

The sibling function get_post_custom_keys() already implements an is_array() check. Adding the same check to get_post_custom_values() prevents this invalid operation at the logic level rather than relying on PHP's error suppression.

Testing on PHP 8.2.12 with E_ALL shows that while the function returns null without crashing (thanks to the null coalescing operator), it still performs an invalid offset access on a boolean when get_post_custom() fails. This is a silent failure that violates defensive coding standards.

<?php
add_action( 'wp_ajax_test_65044', function() {
    $id = 999999;
    $values = get_post_custom_values( 'some_key', $id );
        var_dump(error_reporting());
    wp_send_json_success( array( 'result' => $values ) );
});


#6 @darshitrajyaguru97
4 months ago

Additional Testing & Validation

I’ve tested this behavior on PHP 8.2 with E_ALL enabled and can confirm the inconsistency described in this ticket.

While get_post_custom_values() does not produce a fatal error and ultimately returns null, it still attempts to access an array offset on a non-array value when get_post_custom() fails.

Example test:

add_action( 'wp_ajax_test_65044', function() {
    $id = 999999; // Non-existent post ID
    $values = get_post_custom_values( 'some_key', $id );

    var_dump( error_reporting() );

    wp_send_json_success( array( 'result' => $values ) );
});

In this case, get_post_custom() returns false (or an empty string), and get_post_custom_values() proceeds with:

$custom[ $key ]

This results in an invalid offset access on a boolean/string value. Although PHP 8+ may not always surface it visibly (depending on error handling), it still constitutes a silent failure and violates defensive coding practices.

Consistency with Sibling Function

The related function get_post_custom_keys() already includes a safeguard:

if ( ! is_array( $custom ) ) {
    return;
}

Adding the same is_array() check in get_post_custom_values() would:

  • Prevent invalid offset access at the logic level
  • Align behavior with its sibling function
  • Avoid silent failures under stricter error reporting environments

Recommendation

Introduce a defensive check before accessing the array:

$custom = get_post_custom( $post_id );

if ( ! is_array( $custom ) ) {
    return null;
}

return isset( $custom[ $key ] ) ? $custom[ $key ] : null;

This is a minimal, backward-compatible fix that improves robustness and consistency across the API.

#7 @liaison
4 months ago

  • Keywords has-test-info added

This ticket was mentioned in PR #11640 on WordPress/wordpress-develop by @liaison.


4 months ago
#8

Description
This PR improves the robustness of get_post_custom_values() by adding an explicit array check before accessing the data.

Currently, get_post_custom_values() assumes that get_post_custom() always returns an array. However, in certain scenarios (such as an invalid post_id or when no global post is set), get_post_custom() can return false. Attempting to access an array offset on a boolean value triggers a "Cannot use a scalar value as an array" warning in PHP 8.2+.

Changes included in this PR:

Array Validation: Added an is_array() check in get_post_custom_values() to safely handle non-array returns from get_post_custom().

Consistency: Aligns the behavior of get_post_custom_values() with get_post_custom_keys(), ensuring both return early when data is unavailable.

Unit Tests: Introduced a new test file tests/phpunit/tests/post/getPostCustom.php covering happy paths, invalid IDs, and empty key scenarios to prevent future regressions.

Trac ticket: https://core.trac.wordpress.org/ticket/65044

Use of AI Tools
AI assistance: Yes
Tool(s): Gemini
Model(s): Gemini 3 Flash
Used for: Structuring the PHPUnit test suite and drafting the technical justification for the PR. I have personally verified the behavior against PHP 8.2 and ensured the logic aligns with WordPress Core’s historical handling of post meta.

This Pull Request is for code review only. Please keep all other discussion in the Trac ticket. Do not merge this Pull Request. See GitHub Pull Requests for Code Review in the Core Handbook for more details.

#9 @liaison
4 months ago

  • Keywords has-unit-tests added; needs-unit-tests removed

#10 @westonruter
4 months ago

@liaison You do not need to share such a screenshot. The PR job status is shared where the pull requests are listed.

This ticket was mentioned in Slack in #core-test by r1k0. View the logs.


3 months ago

#12 @ozgursar
3 months ago

Patch Testing Report

Patch Tested: https://github.com/WordPress/wordpress-develop/pull/11496

Environment

  • WordPress: 7.1-alpha-62161-src
  • PHP: 8.2.29
  • Server: nginx/1.29.4
  • Database: mysqli (Server: 8.4.7 / Client: mysqlnd 8.2.29)
  • Browser: Opera
  • OS: macOS
  • Theme: Twenty Twenty-Five 1.5
  • MU Plugins: None activated
  • Plugins:
    • Code Snippets 3.9.6
    • Test Reports 1.2.1

Steps taken

  1. Add the following code snippet to your active theme's functions.php or via Code Snippets plugin:
add_action( 'init', function () {
    // Confirm what get_post_custom() returns for both PR cases
    unset( $GLOBALS['post'] );
    $case1 = get_post_custom( 0 );
    error_log( '[65044] Case 1 — invalid post ID 0: (' . gettype( $case1 ) . ') ' . var_export( $case1, true ) );

    add_filter( 'get_post_metadata', '__return_empty_string', 1 );
    $case2 = get_post_custom( 1 );
    remove_filter( 'get_post_metadata', '__return_empty_string', 1 );
    error_log( '[65044] Case 2 — filter returns "": (' . gettype( $case2 ) . ') ' . var_export( $case2, true ) );

    // Detect whether the is_array guard exists in get_post_custom_values
    $src = file_get_contents( ABSPATH . 'wp-includes/post.php' );
    preg_match( '/function get_post_custom_values.*?(?=\nfunction )/s', $src, $m );
    $guarded = isset( $m[0] ) && str_contains( $m[0], 'is_array' );
    error_log( '[65044] is_array guard in get_post_custom_values: ' . ( $guarded ? 'PATCHED' : 'UNPATCHED' ) );
} );
  1. Ensure WP_DEBUG and WP_DEBUG_LOG are set to true in wp-config.php, then visit any page as an admin and check wp-content/debug.log
  2. Apply the patch and reload the page to observe the change in debug.log
  3. ✅ Patch is solving the problem

Expected result

  • debug.log before patch:

[14-May-2026 15:55:09 UTC] [65044] Case 1 — invalid post ID 0: (boolean) false
[14-May-2026 15:55:09 UTC] [65044] Case 2 — filter returns "": (string)
[14-May-2026 15:55:09 UTC] [65044] is_array guard in get_post_custom_values: UNPATCHED

  • debug.log after patch:

[14-May-2026 15:56:04 UTC] [65044] Case 1 — invalid post ID 0: (boolean) false
[14-May-2026 15:56:04 UTC] [65044] Case 2 — filter returns "": (string)
[14-May-2026 15:56:04 UTC] [65044] is_array guard in get_post_custom_values: PATCHED

Additional Notes

  • Case 1: get_post_custom(0) returns false because get_metadata_raw() short-circuits immediately when the post ID resolves to 0 (no global $post, no explicit ID passed).
  • Case 2: get_post_custom() returns "" when a filter on get_post_metadata short-circuits the metadata lookup.

@westonruter commented on PR #11496:


3 months ago
#13

Could you add a test case for this change that the new code is covered?

Tests added in https://github.com/WordPress/wordpress-develop/pull/11640

#14 @westonruter
3 months ago

  • Keywords needs-testing removed
  • Owner changed from saratheonline to westonruter
  • Status assignedreviewing

Once trunk is open for 7.1 commits, I can commit this.

#15 @audrasjb
3 months ago

Removing trunk version as this is not going to be shipped with WP 7.0 but in the next releases.

@wildworks commented on PR #11496:


3 weeks ago
#16

Closing this in favor of #11640.

#17 @wildworks
3 weeks ago

Once trunk is open for 7.1 commits, I can commit this.

@westonruter, do you think https://github.com/WordPress/wordpress-develop/pull/11640 is ready to commit?

This ticket was mentioned in Slack in #core by adrianduffell. View the logs.


13 days ago

#19 @adrianduffell
13 days ago

  • Keywords early added
  • Milestone 7.1Future Release

This was discussed in today's bug scrub. I’d like to punt it since 7.1 RC 1 is due in about 48 hours time. It looks valuable to fix soon, so tagging with early for consideration in 7.2.

Note: See TracTickets for help on using tickets.