3

I've ran into an issue here where wp_debug notices foul up my ajax response.

The codex suggests using ob_clean just before returning or echoing:

To parse AJAX, WordPress must be reloaded through the admin-ajax.php script, which means that any PHP errors encountered in the initial page load will also be present in the AJAX parsing. If error_reporting is enabled, these will be echoed to the output buffer, polluting your AJAX response with error messages.

Because of this, care must be taken when debugging Ajax as any PHP notices or messages returned may confuse the parsing of the results or cause your Javascript to error.

One option if you can't eliminate the messages and must run with debug turned on is to clear the buffer immediately before returning your data.

So I've done this at the end of my ajax function below but this creates a ob_clean(): failed to delete buffer. No buffer to delete.

While that buffer may be empty, there are still errors messages making their way into my response, which the Codex seems to suggest would be cleard by ob_clean. Further Im using exit() to return results, and the comments in the man page for suggest that it too would flush the buffer. I know there are ways within wp_config.php to conditionally disable wp_debug, but I'd really like this handled in the plugin itself.

Please forgive that I've included the bulk of the function below. I just dont know how to deal with this.

/*
 * Process the ajax request
 * add_action('wp_ajax_title_check', 'duplicate_title_check_callback');
 * action must appear in the main plugin file, not in as separate ajax function file
 * 
 * @uses:       title_check
 * @global:     $wpdb
 * @wp-hook:    wp_ajax_title_check
 *
 */

    function duplicate_title_check_callback()
{
    global $wpdb;
    $title = $_POST['post_title'];
    $post_id = $_POST['post_id'];
    header('Content-Type: application/json');
    $sim_query = "SELECT ID FROM $wpdb->posts WHERE post_status = 'publish' AND post_type = 'venue' AND post_title LIKE '%%%s%%' AND ID != '%d'";
    $sim_results = $wpdb->get_results( $wpdb->prepare( $sim_query, like_escape($title), $post_id ) );
    if ($sim_results)
    {
        $notice = array("head" => "Whooa there! We found the following venue(s) with a similar heading:", "foot" =>"Consider deleting any duplicates and/or moving this post to the trash.");
        foreach ($sim_results as $sim_result)
        {
            $venue['title'] = get_the_title($sim_result->ID);
            $path = 'post.php?post=' . $sim_result->ID . '&action=edit';
            $venue['link'] = esc_url(admin_url($path));
            $venue['city'] =  wpcf_api_field_meta_value( 'city', $sim_result->ID );
            $venue['ID'] =  $sim_result->ID;
            $posts[] = $venue;
        }
        $return_json = array("status" => "true", "notice" => $notice, "posts"=>$posts );
    } else {
        $return_json = array("status" => "false", "notice" => "This Venue title looks unique!");
    }
    ob_clean();
    exit(json_encode($return_json)); // This should flush buffers to default point http://php.net/manual/en/function.exit.php#101204
    //echo(json_encode($return_json)); // Same result
    wp_die(); // terminate the wp instance, exit should be sufficient.
}

I have also have seen: How to define WP_DEBUG as true outside of wp-config.php?

EDIT: After advice from @czerspalace and a great writeup by @bosco this is what I've arrived at for the now. A few cascading errors were largely the issue, but a fuller implementation of bosco's 'more robust' method would actually catch and log errors. In principle I understand, but given my experience so far, I don't expect the addition of if( ob_get_length() ) ob_clean(); to actually clear the buffer, as its not been working thus far. But its a step in the right direction, and given that the check returns the header call to a more appropriate location - that's a plus too. Gentlemen, thank you.

function duplicate_title_check_callback()
{
    global $wpdb;
    $title = $_POST['post_title'];
    $post_id = $_POST['post_id'];
    $sim_query = "SELECT ID FROM $wpdb->posts WHERE post_status = 'publish' AND post_type = 'venue' AND post_title LIKE '%%%s%%' AND ID != '%d'";
    $sim_results = $wpdb->get_results( $wpdb->prepare( $sim_query, $wpdb->esc_like($title), $post_id ) );
    if ($sim_results)
    {
        $notice = array("head" => "Whooa there! We found the following venue(s) with a similar heading:", "foot" =>"Consider deleting any duplicates and/or moving this post to the trash.");
        foreach ($sim_results as $sim_result)
        {
            $venue['title'] = get_the_title($sim_result->ID);
            $path = 'post.php?post=' . $sim_result->ID . '&action=edit';
            $venue['link'] = esc_url(admin_url($path));
            $venue['city'] =  wpcf_api_field_meta_value( 'city', $sim_result->ID );
            $venue['ID'] =  $sim_result->ID;
            $posts[] = $venue;
        }
        $return_json = array("status" => "true", "notice" => $notice, "posts"=>$posts );
    }
    else
    {
        $return_json = array("status" => "false", "notice" => "This Venue title looks unique!");
    }
    if( ob_get_length() )
        ob_clean();
        header('Content-Type: application/json');
        // http://php.net/manual/en/function.exit.php#101204
        exit(json_encode($return_json));

}
12
  • What kind of errors are being returned? What if you place the ob_clean before the return in title_check? Apr 14, 2015 at 20:32
  • Don't put a function inside a function. And there's no need to flush the buffer. Just echo the results of json_encode, then exit. Apr 14, 2015 at 21:13
  • @TheDeadMedic your advice is good and I've taken a revision. But the result is the same. @czerspalace with this refactoring the result is the same as your suggestion no? Yes a better block of code, but still: PHP Notice: ob_clean(): failed to delete buffer. No buffer to delete in ....
    – orionrush
    Apr 14, 2015 at 21:52
  • Remove your ob_clean call. I can't see why you need it? Apr 14, 2015 at 21:53
  • Aside from PHP Notice: ob_clean(): failed to delete buffer. No buffer to delete in .... are you receiving any other error messages? Apr 14, 2015 at 21:54

1 Answer 1

2

"How can I prevent unexpected output from reaching the browser/interfering with my AJAX response?"

The Problem

Something in your installation is generating "unexpected output," that is, it's creating content or data which should not be there if WordPress is running smoothly. This could imply a misconfigured server environment, a faulty WordPress installation, a bad database connection, an old plugin, a theme attempting to display a content when it shouldn't, or problems in your own code - the point is any error, content, or notice generated for any reason is popping up where none should be.

The most stable WordPress environments tend to be those which generate no extraneous output or messages, even when WP_DEBUG is enabled; so the best possible course of action is to examine that unexpected output, determine what it's source is, and addressing the issues such that the only output is a proper, clean response. Ideally, a production installation should generate no errors, warnings, or notices at all.

Over the course of development however, it can be beneficial to momentarily ignore unexpected output coming from sources you are not currently addressing, or that are perhaps beyond your control (in the case of the source being another team member's responsibility, for instance, or a theme you must work with, but has yet to receive an expected update). And should non-critical errors arise in production, you certainly don't want them interfering with the end-user's experience; the best course of action would be to log them to file such that you may address them later.

The fact remains: the only true solution is to locate the individual problems creating unexpected output and solve them one-by-one. Everything else should be regarded as a temporary "hack."

The Quick Fix

To answer your question in short, ob_clean() will indeed fail if there either is no buffer, or the buffer is empty. If you still receive unexpected output after a successful call to ob_clean(), the implication would be that your code is creating output, somehow - in the code provided, perhaps the call to exit(json_encode($return_json));, or maybe even the failed call to ob_clean() itself. Adding a check to only clean a pre-existing non-empty buffer may be the solution you're looking for (on a side note, place the header('Content-Type: application/json'); call immediately before you output the response to prevent ob_clean() from sweeping away the header in the case of nested buffers, or any header()-related errors):

if( ob_get_length() )
  ob_clean();

header('Content-Type: application/json');
echo json_encode( $return_json );
exit;

If you still receive unexpected output at this point, json_encode() is likely choking up trying to process $return_json - double check the variable's contents and json_encode()'s requirements.

If the problem persists, forcing an output buffer in combination to the above may do the trick. This is more of a speculative hack than a legitimate solution, though, and would best be avoided. Try one of the following:

  • Change the value of output buffering in your php.ini configuration file (location dependendant on server environment) and restart your webserver: output_buffering on
  • Attempt to enable output buffering at the script-level. Add the following line to wp-config.php: ini_set( 'output_buffering', 'on' );
    • Failing that, a simple call to ob_start() in the same file may yield results, but comes with a risk of interfering with more complex environments and installations.
  • If PHP is running as an Apache module, you may be able to enable output buffering from the .htaccess file in the root of your WordPress installation:

<IfModule mod_php5.c> php_value output_buffering On </IfModule>

If none of the above works, I'm afraid I'm out of ideas!


A More Robust Hack

I've taken something of a complex approach to solving this potential problem while working on my own plugins, taking into account the possibility that other code beyond my control is generating unexpected output as well as my own - I must admit it's not been thoroughly tested, but so far has served me well. There are no doubt simpler solutions, but I intend to provide ideas for a more useful approach than that suggested in the codex. I'm fairly certain my own implementation has room for a lot of improvement, as well.

PHP controls output with a stack of nested output buffers, and depending on which server and PHP extensions are installed and enabled (as well as the function of the PHP script itself), additional buffers may be sitting on top of that of PHP's default one. Or there may not even be an output buffer (which may be the case if the PHP ini directive output_buffering is disabled and the script itself hasn't created one).

I should note that several PHP extensions use buffers to do things like URL rewriting and output compression - my solution below should be used for troubleshooting and development purposes only, as will almost certainly prevent these mechanisms from functioning properly on your AJAX responses. Usually this effect is negligible, but it can create issues in more complex environments and sites. The hack described in the Codex runs a similar risk, although lesser as it does not completely obliterate buffers as I do.

So, when I begin debugging an AJAX handler, I first dump the contents of any nested buffers and explicitly discard each buffer from the stack, then use the uppermost buffer (or create one if it does not exist) to capture any messages generated over the course of handling the AJAX request (as well as any contained in a pre-existing top-level buffer):

if( defined( 'DOING_AJAX' ) && DOING_AJAX ) {
  $bufferContents = array();

  // Capture nested buffer contents and discard them
  while( 1 < ob_get_level() )
    $bufferContents[] = ob_get_clean();

  // Ensure that a top-level buffer is available to capture any unexpected output
  if( ! ob_get_level() )
    ob_start();
}

The $bufferContents array can then be written to a debug log, or recorded elsewhere for reference, if desired.

After handling the request and immediately before dispatching a response, I capture the contents of my new top-level buffer, which now contains any unexpected output relevant to my AJAX handler script:

// If the output buffer contains data, get rid of it to prevent mucking up the JSON response
if( 0 < ( $bufferLength = ob_get_length() ) ) {
  $bufferContents = ob_end_clean();
}

header( "Content-Type: application/json" );
echo $ajaxResponse;
exit;

Once more, the $bufferContents variable likely contains useful information and can be handled as desired.

More specifically, I like to respond to AJAX requests with a JSON object - when debugging is enabled I append the buffer's contents to the object such that I may inspect it in Chrome's inspector console, as well as saving it to a file regardless of whether or not debugging is enabled. For example:

$ajaxResponse  = array(
  'status' => $status || 'error',
  'data'   => $response
);

// If the output buffer contains data, get rid of it to prevent mucking up the JSON response
if( 0 < ( $bufferLength = ob_get_length() ) ) {
  $bufferContents = ob_end_clean();

  // If debugging is enabled, pass any unexpected output to the client in the form of an additional 'phpBuffer' property
  if( WP_DEBUG ) {
    $ajaxResponse[ 'phpBuffer' ] => $bufferContents;
  }

  // Take note of the situation in the log files
  if( WP_DEBUG_LOG ) {
    $bufferLogFile  = plugin_dir_path( __FILE__ ) . 'debug.buffer.log';
    $bufferContents = date('m/d/Y h:i:s a', time()) . ':' . chr(10) . $bufferContents . chr(10) . chr(10);

    error_log( $bufferLength . ' characters of unexpected output were generated while processing an AJAX request in "' . plugin_dir_path( __FILE__ ) . __FILE__ . '". They have been recorded in "' . $bufferLogFile . '".';

    // Save the buffer contents to file.
    file_put_contents( $bufferLogFile, $bufferContents, FILE_APPEND );
  }
}

header( "Content-Type: application/json" );
echo json_encode( $ajaxResponse );
exit;
3
  • Thanks for the detailed write up! I will digest, but regarding the headers, I initially had them where you suggested, but received headers already sent warnings. I moved it up to the position until they disappeared.
    – orionrush
    Apr 14, 2015 at 22:20
  • Also, regardless of the use or warnings related to ob_clean, I'm still seeing the notices make their way through to the response, so a check won't affect the results. This would seem evidence that ob_clean, isn't actually doing what the codex suggest. joson_encode however does just fine, when wp_debug is disabled. So this leads me to think that the notices are causing the issue, not the construction of json itself. a quandry - I will double check the json results to be sure.
    – orionrush
    Apr 14, 2015 at 22:26
  • given what Im seeing - I'm going to work in your second approach. I'd like this to be as robust as possible without making demands on the environment. I can see having to move hosts for example in a year, and I'd hate to overcomplicate this, in case its not me who does it. It will take a few days before I can get to this but will come back with comments. Many thanks again for sharing your approach - very helpful indeed.
    – orionrush
    Apr 14, 2015 at 22:42

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.