Skip to content
Open
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
0a06f41
Fixed rest_handle_doing_it_wrong to manage debug messages properly - …
ilclaudio Mar 25, 2026
6c3e353
Fixed rest_handle_doing_it_wrong: added headers check
ilclaudio Mar 25, 2026
610ac74
REST API: Use with 'PHP Notice:' prefix in rest_handle_doing_it_wron…
ilclaudio Mar 26, 2026
6c7c2d4
Merge remote-tracking branch 'upstream/trunk' into fix/64260-rest-doi…
ilclaudio Apr 6, 2026
8bb3815
REST API: Add caller location to rest_handle_doing_it_wrong() error log
ilclaudio Apr 6, 2026
40c4f98
Merge branch 'trunk' into fix/64260-rest-doing-it-wrong-log
westonruter May 14, 2026
0a98d18
Update src/wp-includes/rest-api.php
ilclaudio May 15, 2026
922b735
Update src/wp-includes/rest-api.php
ilclaudio May 15, 2026
043f126
Merge branch 'trunk' into fix/64260-rest-doing-it-wrong-log
ilclaudio Jun 1, 2026
82e4919
Merge remote-tracking branch 'upstream/trunk' into fix/64260-rest-doi…
ilclaudio Jun 6, 2026
3829aed
Update src/wp-includes/rest-api.php
ilclaudio Jun 6, 2026
9b55875
Update src/wp-includes/rest-api.php
ilclaudio Jun 6, 2026
db266e1
Merge branch 'fix/64260-rest-doing-it-wrong-log' of https://github.co…
ilclaudio Jun 6, 2026
d6e9fa5
Merge branch 'trunk' into fix/64260-rest-doing-it-wrong-log
ilclaudio Jun 6, 2026
5777f1a
REST API: Fix path comparison and backtrace depth in rest_handle_doin…
ilclaudio Jun 6, 2026
dca14ad
Merge branch 'fix/64260-rest-doing-it-wrong-log' of https://github.co…
ilclaudio Jun 6, 2026
8943b90
Merge remote-tracking branch 'upstream/trunk' into fix/64260-rest-doi…
ilclaudio Jun 7, 2026
33550a9
Merge remote-tracking branch 'upstream/trunk' into fix/64260-rest-doi…
ilclaudio Jun 10, 2026
a4076db
REST API: Extend debug logging to deprecated function and argument ha…
ilclaudio Jun 10, 2026
8329da1
REST API: Fix case-insensitive path comparison in _rest_get_debug_bac…
ilclaudio Jun 10, 2026
62deb78
Merge branch 'trunk' into fix/64260-rest-doing-it-wrong-log
ilclaudio Jun 10, 2026
3b7826d
Update src/wp-includes/rest-api.php
ilclaudio Jun 14, 2026
b714964
Update src/wp-includes/rest-api.php
ilclaudio Jun 14, 2026
546dc07
Update src/wp-includes/rest-api.php
ilclaudio Jun 14, 2026
8fce388
Update src/wp-includes/rest-api.php
ilclaudio Jun 14, 2026
d88a269
Update src/wp-includes/rest-api.php
ilclaudio Jun 14, 2026
19b5c38
Merge remote-tracking branch 'upstream/trunk' into fix/64260-rest-doi…
ilclaudio Jun 14, 2026
2f6ed99
REST API: Remove strtolower() from path comparison in _rest_get_debug…
ilclaudio Jun 14, 2026
aac1be5
Merge branch 'trunk' into fix/64260-rest-doing-it-wrong-log
ilclaudio Jun 17, 2026
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
95 changes: 89 additions & 6 deletions src/wp-includes/rest-api.php
Original file line number Diff line number Diff line change
Expand Up @@ -702,6 +702,71 @@ function rest_ensure_response( $response ) {
return new WP_REST_Response( $response );
}

/**
* Returns a formatted caller location string for REST API debug log entries.
*
* Searches the call stack for the first frame whose file lives inside WP_CONTENT_DIR,
* identifying the plugin or theme that triggered the notice. Used by the REST API
* debug handlers to append actionable location info to error_log() output.
*
* @since 7.1.0
* @access private
*
* @return string Formatted string such as ' called from my_func() in /path/plugin.php on line 8',
* or ' called from (anonymous function) in /path/plugin.php on line 8' for closures,
* or empty string when no plugin/theme frame is found in the call stack.
*/
function _rest_get_debug_backtrace_caller(): string {
$backtrace = debug_backtrace( DEBUG_BACKTRACE_IGNORE_ARGS );
$normalized_content_dir = trailingslashit( wp_normalize_path( WP_CONTENT_DIR ) );

foreach ( $backtrace as $i => $frame ) {
if ( ! isset( $frame['file'] ) ) {
continue;
}

$normalized_file = wp_normalize_path( $frame['file'] );
if ( ! str_starts_with( $normalized_file, $normalized_content_dir ) ) {
continue;
}

$location = ' in ' . $normalized_file;
if ( isset( $frame['line'] ) ) {
$location .= ' on line ' . $frame['line'];
}

/*
* The next frame holds the function that contains the offending call.
* PHP represents closures as '{closure}' (< 8.4) or '{closure:file:line}' (>= 8.4),
* both starting with '{'. These are replaced with a readable label.
*/
$next = $backtrace[ $i + 1 ] ?? null;
if ( $next ) {
if ( str_starts_with( $next['function'], '{' ) ) {
return ' called from (anonymous function)' . $location;
}
Comment thread
ilclaudio marked this conversation as resolved.

/*
* A call made from a file's global scope has no enclosing function; PHP
* reports the include/require pseudo-function that loaded the file instead.
* In that case only the location is meaningful.
*/
if ( in_array( $next['function'], array( 'require', 'require_once', 'include', 'include_once' ), true ) ) {
return $location;
}
$caller_func = $next['function'];
if ( isset( $next['class'] ) ) {
$caller_func = $next['class'] . $next['type'] . $caller_func;
}
return ' called from ' . $caller_func . '()' . $location;
}

return $location;
}

return '';
}

/**
* Handles _deprecated_function() errors.
*
Expand All @@ -712,7 +777,7 @@ function rest_ensure_response( $response ) {
* @param string $version Version.
*/
function rest_handle_deprecated_function( $function_name, $replacement, $version ) {
if ( ! WP_DEBUG || headers_sent() ) {
if ( ! WP_DEBUG ) {
return;
}
if ( ! empty( $replacement ) ) {
Expand All @@ -723,7 +788,13 @@ function rest_handle_deprecated_function( $function_name, $replacement, $version
$string = sprintf( __( '%1$s (since %2$s; no alternative available)' ), $function_name, $version );
}

header( sprintf( 'X-WP-DeprecatedFunction: %s', $string ) );
if ( ! headers_sent() ) {
header( sprintf( 'X-WP-DeprecatedFunction: %s', $string ) );
}

if ( WP_DEBUG_LOG && ( error_reporting() & E_USER_DEPRECATED ) ) {
error_log( 'PHP Deprecated: ' . wp_strip_all_tags( $string ) . _rest_get_debug_backtrace_caller() );
}
}

/**
Expand All @@ -736,7 +807,7 @@ function rest_handle_deprecated_function( $function_name, $replacement, $version
* @param string $version Version.
*/
function rest_handle_deprecated_argument( $function_name, $message, $version ) {
if ( ! WP_DEBUG || headers_sent() ) {
if ( ! WP_DEBUG ) {
return;
}
if ( $message ) {
Expand All @@ -747,7 +818,13 @@ function rest_handle_deprecated_argument( $function_name, $message, $version ) {
$string = sprintf( __( '%1$s (since %2$s; no alternative available)' ), $function_name, $version );
}

header( sprintf( 'X-WP-DeprecatedParam: %s', $string ) );
if ( ! headers_sent() ) {
header( sprintf( 'X-WP-DeprecatedParam: %s', $string ) );
}

if ( WP_DEBUG_LOG && ( error_reporting() & E_USER_DEPRECATED ) ) {
error_log( 'PHP Deprecated: ' . wp_strip_all_tags( $string ) . _rest_get_debug_backtrace_caller() );
}
}

/**
Expand All @@ -760,7 +837,7 @@ function rest_handle_deprecated_argument( $function_name, $message, $version ) {
* @param string|null $version The version of WordPress where the message was added.
*/
function rest_handle_doing_it_wrong( $function_name, $message, $version ) {
if ( ! WP_DEBUG || headers_sent() ) {
if ( ! WP_DEBUG ) {
return;
}

Expand All @@ -774,7 +851,13 @@ function rest_handle_doing_it_wrong( $function_name, $message, $version ) {
$string = sprintf( $string, $function_name, $message );
}

header( sprintf( 'X-WP-DoingItWrong: %s', $string ) );
if ( ! headers_sent() ) {
header( sprintf( 'X-WP-DoingItWrong: %s', $string ) );
}

if ( WP_DEBUG_LOG && ( error_reporting() & E_USER_NOTICE ) ) {
error_log( 'PHP Notice: ' . wp_strip_all_tags( $string ) . _rest_get_debug_backtrace_caller() );
}
}

/**
Expand Down
Loading