15

I recently updated a WordPress instance from 5.4 to 5.5 to end up with an annoying margin-top in the admin area, above the menu directly.

enter image description here

I deactivated all plugins, switched themes, did some inspection, and I ended up with some findings.

The CSS class causing the margin-top is .php-error, which is appended to #adminmenuwrap when a php error is supposed to be displayed.

/* in load-styles.php */
.php-error #adminmenuback, .php-error #adminmenuwrap {
    margin-top: 2em;
}

/* the menu wrapper */
<div id="adminmenuwrap"></div> 

/* the php script in /wp-admin/admin-header.php line 201 */
// Print a CSS class to make PHP errors visible.
if ( error_get_last() && WP_DEBUG && WP_DEBUG_DISPLAY && ini_get( 'display_errors' ) ) {
    $admin_body_class .= ' php-error';
}

/* print_r(error_get_last()) outputs */
Array ( 
[type] => 8 
[message] => unserialize(): Error at offset 11857 of 11895 bytes 
[file] => .../wp-includes/functions.php 
[line] => 624
)

/**
 * Unserialize data only if it was serialized.
 *
 * @since 2.0.0
 *
 * @param string $data Data that might be unserialized.
 * @return mixed Unserialized data can be any type.
 */
function maybe_unserialize( $data ) {
    if ( is_serialized( $data ) ) { // Don't attempt to unserialize data that wasn't serialized going in.
        return @unserialize( trim( $data ) );
    }

    return $data;
}

It is perfectly normal that WP_DEBUG && WP_DEBUG_DISPLAY && ini_get( 'display_errors' ) be true (because I'm actually debugging), but the problem is that no php error is being displayed.

This instance (with bug) is running on an online hosted server. But I also have the exact same copy of this instance running on localhost, except it does not have this bug.

Did anyone encounter this scenario? What do you suggest?

------- EDIT (Fix) --------

The following manipulation did solve the problem but I'm still not sure about the origin or "real" cause behind the bug. This been said, it was a calculation error in serialized data, more precisely, in my case it came from the contents of Privacy Policy page sample (tutorial by WordPress).

Here's how I went about it:

// Edit the function in /wp-includes/functions.php on line 624 and include some 
// error logging. 
// DO NOT FORGET TO REVERT BACK TO THE ORIGINAL CODE ONCE DONE DEBUGGING!

/**
 * Unserialize data only if it was serialized.
 *
 * @since 2.0.0
 *
 * @param string $data Data that might be unserialized.
 * @return mixed Unserialized data can be any type.
 */
function maybe_unserialize( $data ) {
    if ( is_serialized( $data ) ) { // Don't attempt to unserialize data that wasn't serialized going in.
        error_log( "DATA DEBUGGING START ---------- \r");
        error_log( "TRIMED: ");
        error_log( trim( $data ) );
        error_log( "UNSERIALIZED: ");
        error_log( print_r(unserialize( trim( $data ) ), true));
        error_log( "DATA DEBUGGING END ---------- \r\n");
        return unserialize( trim( $data ) );
    }

    return $data;
}

This will log all serialized and unserialized values in your debug.log or error.log depending which method you are using. I'm using the default WordPress define( 'WP_DEBUG_LOG', true ); in w-config.php, which logs errors in the file debug.log under /wp-content/.

Doing this allowed me to detect the exact row in database causing the problem. The problem comes from a wrong count calculation.

a:3:{s:11:"plugin_name";s:9:"WordPress";s:11:"policy_text";s:11789:".....

I did a characters/bytes count of the contents of that key and it turned out to be 11799 instead of 11789.

The value in s:11789 must be s:11799 in my case. So I changed it in the database and everything worked fine. I also edited the page in the Editor and saved it then rechecked and everything still work fine.

This fixed the issue but I guess something went wrong at some point. Most probably when I imported the local database to a different instance.

I hope this helps!

1

5 Answers 5

8

I ran into that issue too, and it turns out that it was because there actually was an error that wasn't displaying. Once I fixed that underlying error, the top margin problem went away.

This is in wp-admin/admin-header.php:

// Print a CSS class to make PHP errors visible.
if ( error_get_last() && WP_DEBUG && WP_DEBUG_DISPLAY && ini_get( 'display_errors' ) ) {
    $admin_body_class .= ' php-error';
}

So I temporarily added a display of that error_get_last() output to one of my plugins:

$debug = print_r(error_get_last(),true);
echo '<p>php-error: '.esc_attr($debug).'</p>';

That showed me where the underlying error was. I fixed it, and problem solved!

5
  • Thanks for the answer, but that is exactly what I did as you can see in my question. I went down to determine the source of the error, except that the reason is in the mechanism of WordPress itself regarding the serialization and unserialization of data. I doing more debugging but so far no solution yet
    – Braza
    Aug 14, 2020 at 20:05
  • 1
    My apologies, I didn't read far enough into your post. But maybe this will help... I found that the .php-error class appears even if the error output is hidden by the @ character. So, if you can recreate the bug somewhere not on production, temporarily change this: return @unserialize( trim( $data ) ); ...to this: return unserialize( trim( $data ) ); And see if that tells you what's going on?
    – RJR
    Aug 15, 2020 at 19:31
  • Sure, that was among the first things that i tries, removing the @ suppressor shows the same error that I mention in my question. Check /* print_r(error_get_last()) outputs */ in question ;). What I'm trying to do is figure out which row(s) in database are causing this error. Hopefully it is just one malformed serialization.
    – Braza
    Aug 17, 2020 at 9:10
  • The error I got by using your method was; hp-error: Array ( [type] => 32 [message] => Module 'sqlite3' already loaded [file] => Unknown [line] => 0 ) whoich I'm pretty sure is not me!
    – Chris Pink
    Dec 21, 2020 at 14:37
  • Really good solution to find the error when debug is true and the error not displayed as expected. echo "<pre>error_last\n"; print_r( error_get_last() ); echo "</pre>\n"; die();
    – xavier bs
    Dec 2, 2021 at 14:53
1

You can also set WP_DEBUG to false in your wp-config.php file and this will no longer show (the .php-error class will no longer be generated). This is how you should have it set on any production site anyways.

If debug mode is on and you have errors printing to the screen sometimes the first 30-40 characters sometimes get covered by fixed position left admin menu, so I think this is a newly added "solution" for that issue.

But, yeah as Robert said - the best solution is to fix the error. But sometimes you can't do that if it's just a notice or warning from a 3rd party plugin or theme - so just make sure debug is off in production and you should be fine.

3
  • Thanks for the suggestion @squarecandy. I'm aware that WP_DEBUG and WP_DEBUG_LOG and WP_DEBUG_DISPLAY must be off on production site. My main concern here is to fix the cause, not remedy the consequence.
    – Braza
    Aug 20, 2020 at 9:19
  • Well if you're in debug mode and the margin appears - the margin is a feature and the error is the bug... so you seem to be asking the question "how do I make the space caused by .php-error go away" but then answering the question "how do I troubleshoot PHP errors on my WordPress site"... if you have an error and the margin appears, that is a works-as-intended situation. Aug 20, 2020 at 18:24
  • I'm asking "How to fix the admin menu margin-top bug in WordPress 5.5?" which means fixing it, or fixing the bug or the feature or anything that is causing it, the more precise the better. What you suggest is to ignore the error and the bug and just turn debugging off. That is not a fix, nor a solution. This is running away from a bug.
    – Braza
    Aug 20, 2020 at 19:38
1

To elaborate more on the answer of Braza. If there is a length mismatch within one of your database values, you can track it down by doing a regex search and checking if the string length matches.

I've extended Braza's code to make this easier:

/**
 * Unserialize data only if it was serialized.
 *
 * @since 2.0.0
 *
 * @param string $data Data that might be unserialized.
 * @return mixed Unserialized data can be any type.
 */
function maybe_unserialize( $data ) {
    if ( is_serialized( $data ) ) { // Don't attempt to unserialize data that wasn't serialized going in.
        preg_match_all('/s:([0-9]*):"/', $data, $matches, PREG_OFFSET_CAPTURE);
        foreach ($matches[1] as $match) {
            $string = substr($data, (intval($match[1]) + strlen(strval($match[0])) + 1), (intval($match[0]) + 2));
            if (substr($string, -1) != '"') {
                error_log('LENGTH MISMATCH!!! \r');
                error_log($string);
                error_log('CHANGE THIS VALUE: \r');
                error_log($data);
            }
        }
        return unserialize( trim( $data ) );
    }

    return $data;
}
1
  • Thank you Tom Groot.
    – Braza
    Sep 7, 2020 at 11:02
0

This is a fix, not a solution

The following manipulation did solve the problem but I'm still not sure about the origin or "real" cause behind the bug. This been said, it was a calculation error in serialized data, more precisely, in my case it came from the contents of Privacy Policy page sample (tutorial by WordPress).

Here's how I went about it:

// Edit the function in /wp-includes/functions.php on line 624 and include some 
// error logging. 

/**
 * Unserialize data only if it was serialized.
 *
 * @since 2.0.0
 *
 * @param string $data Data that might be unserialized.
 * @return mixed Unserialized data can be any type.
 */
function maybe_unserialize( $data ) {
    if ( is_serialized( $data ) ) { // Don't attempt to unserialize data that wasn't 
serialized going in.
        error_log( "DATA DEBUGGING START ---------- \r");
        error_log( "TRIMED: ");
        error_log( trim( $data ) );
        error_log( "UNSERIALIZED: ");
        error_log( print_r(unserialize( trim( $data ) ), true));
        error_log( "DATA DEBUGGING END ---------- \r\n");
        return unserialize( trim( $data ) );
    }

    return $data;
}

DO NOT FORGET TO REVERT BACK TO THE ORIGINAL CODE ONCE DONE DEBUGGING!

This will log all serialized and unserialized values in your debug.log or error.log depending which method you are using. I'm using the default WordPress define( 'WP_DEBUG_LOG', true ); in w-config.php, which logs errors in the file debug.log under /wp-content/.

Doing this will help detecting the exact row in database which is causing the problem. The problem here comes from a wrong count calculation.

a:3:{s:11:"plugin_name";s:9:"WordPress";s:11:"policy_text";s:11789:".....

I did a characters/bytes count of the contents of that key and it turned out to be 11799 instead of 11789.

The value in s:11789 must be s:11799 in my case. So I changed it in the database and everything worked fine. I also edited the page in the Editor and saved it then rechecked and everything still work fine.

This fixed the issue but I guess something went wrong at some point. Most probably when I imported the local database to a different instance.

I hope this helps!

1
  • Thanks for your answer. To make it more clearly how to fix this (I think the characters count is different on each site). Copy the data inside this double quotes ;s:11799:\"...copy here...\";. As you see, my string count before fix is 11799. Unescape \" to be " on any website for example freeformatter.com/javascript-escape.html > copy unescaped to count characters online on any website for example charactercountonline.com. For my, I counted and got 11787. Update your counted characters to DB. Done.
    – vee
    Dec 27, 2020 at 12:41
0

Just related info.

The topic is clear and the maybe_unserialize() function is a place where an error is happening. We should debug or log it to solve it.

But you can get this issue in any WP version if you have replaced the old domain with the new one in the entire database. It's the wrong way. According to this documentation article:

"If you do a search and replace on your entire database to change the URLs, you can cause issues with data serialization, due to the fact that some themes and widgets store values with the length of your URL marked. When this changes, things break. ... Note: Only perform a search and replace on the wp_posts table."

So, if you got this issue after the site migration, perhaps you changed the database wrong.

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