#59818 closed defect (bug) (fixed)
[PHP 8.1] deprecated null in `explode()` for 'ping_sites' in `wp-admin/options-writing.php` with "Discourage search engines" on
| Reported by: |
|
Owned by: |
|
|---|---|---|---|
| Milestone: | 6.6 | Priority: | normal |
| Severity: | normal | Version: | |
| Component: | Administration | Keywords: | php81 has-patch has-test-info commit |
| Focuses: | php-compatibility | Cc: |
Description
When "Discourage search engines" is on, any update to Dashboard / Writing causes:
Deprecated: explode(): Passing null to parameter #2 ($string) of type string is deprecated in .../wp-includes/formatting.php
In this case, the 'ping_sites' option is not displayed, so the $_POST value is null. The deprecation warning is triggered in wp-includes/formatting.php, in sanitize_option(), "case 'ping_sites':".
Tested on today's nightly, WordPress 6.5-alpha-57063. Also affects latest release, WP 6.3.2.
Attachments (3)
Change History (22)
#1
@
2 years ago
- Keywords needs-patch added
- Milestone changed from Awaiting Review to 6.5
- Version trunk deleted
#2
@
2 years ago
- Keywords has-patch added; needs-patch removed
- Owner set to SergeyBiryukov
- Status changed from new to accepted
#4
follow-up:
↓ 5
@
2 years ago
Thanks for the quick action! I have a different approach that addresses the underling issue.
The controller wp-admin/options.php uses update_option() unconditionally on all relevant elements of $allowed_options, even if they don't appear in $_POST. Luckily in the current codebase, or as part of its design, there is a filter on get_option( 'ping_sites' ), called privacy_ping_filter(), which checks:
if ( '1' === get_option( 'blog_public' ) ) {
return $sites;
} else {
return '';
}
So when update_option() uses get_option() to compare $value to $old_value, it sees '' === '', where explode( "\n", $value ) has cast NULL to ''. Thus no data loss.
My first patch mods $allowed_options. It does it directly, rather than using the filter, since the variable is very specific to the file it is in. Might as well make it easy to read. This patch is tested as far as updating options goes.
My alternate patch also reconciles the strictness and specificity of the evaluation of 'blog_public' across the codebase, except multisite. In other words, '1' === vs. '0' != , etc. It also makes the filter signature in wp-includes/functions.php correct. Multisite seems to have its own way of doing things. This patch is not tested.
#5
in reply to:
↑ 4
@
2 years ago
Can you add the testing instruction please?
Replying to kitchin:
Thanks for the quick action! I have a different approach that addresses the underling issue.
The controller
wp-admin/options.phpusesupdate_option()unconditionally on all relevant elements of$allowed_options, even if they don't appear in$_POST. Luckily in the current codebase, or as part of its design, there is a filter onget_option( 'ping_sites' ), calledprivacy_ping_filter(), which checks:
if ( '1' === get_option( 'blog_public' ) ) { return $sites; } else { return ''; }So when
update_option()usesget_option()to compare$valueto$old_value, it sees'' === '', whereexplode( "\n", $value )has castNULLto''. Thus no data loss.
My first patch mods
$allowed_options. It does it directly, rather than using the filter, since the variable is very specific to the file it is in. Might as well make it easy to read. This patch is tested as far as updating options goes.
My alternate patch also reconciles the strictness and specificity of the evaluation of 'blog_public' across the codebase, except multisite. In other words,
'1' ===vs.'0' !=, etc. It also makes the filter signature inwp-includes/functions.phpcorrect. Multisite seems to have its own way of doing things. This patch is not tested.
#6
@
2 years ago
STR:
- Drop all tables. Delete all files in public_html.
- wget https://wordpress.org/nightly-builds/wordpress-latest.zip
- unzip wordpress-latest.zip
- Move files and upload wp-config.php, with
define('WP_DEBUG', true ); - Visit /wp-admin to install. Check "Discourage search engines" and install.
- Login and go to Settings / Writing.
- Click Save Changes.
Depending on your PHP settings, you will see the error on the screen and/or in wp-admin/error_log.
WordPress 6.5-alpha-57320
Linux 4.18... x86_64
PHP 8.2...
#9
@
20 months ago
Hi @kitchin, thanks for the test instructions. Your 2nd patch does seem to solve the issue and address this behavior, on the single-site installation. I didn't get a chance to test this on the multi-site today, but if someone can share if it addresses the issue, we can get this merged sooner.
#10
@
20 months ago
I downloaded the latest version with wget https://wordpress.org/nightly-builds/wordpress-latest.zip and my version is 6.6-alpha-58025. But I could not reproduce the error. I also checked wp-admin/error_log. but not found anything error code.
wp-admin/error_log.
This ticket was mentioned in Slack in #core by oglekler. View the logs.
18 months ago
#12
@
18 months ago
Reproduction Report
Environment
- OS: macOS 14.5
- Web Server: Nginx
- PHP: 8.3
- WordPress: 6.6-beta2-58411
- Browser: Firefox 126.0.1
- Theme: TT4
- Active Plugins: None
Testing Steps
- Ran
wp db reset --yesto wipe the database. - Walked through the install, while checking "Discourage search engines" option.
- Logged in.
- Navigated to Settings / Writing.
- Clicked "Save Changes" button.
- Then checked the logs.
Actual Results
- ✅ Able to reproduce after clicking the "Save Changes" button.
[14-Jun-2024 15:38:51 UTC] PHP Deprecated: explode(): Passing null to parameter #2 ($string) of type string is deprecated in ./wp-includes/formatting.php on line 4952
#13
follow-up:
↓ 14
@
18 months ago
- Keywords commit added
To evaluate the different patches / approaches in this ticket, I needed to first understand the relationship between 'blog_public' and 'ping_sites'.
Relationship between 'blog_public' and 'ping_sites'
Source: https://developer.wordpress.org/apis/options/#writing
ping_sites: When you publish a new post, WordPress automatically notifies the following site update services. For more about this, see Update Services. Separate multiple service URLs with line breaks. Requiresblog_publicto have a value of 1.
Default: ‘http://rpc.pingomatic.com/’
Data type:String (possibly multi-line)
This relationship shows itself in the options-writing.php code (shown here), as the textarea#ping_sites only renders when '1' === get_option( 'blog_public' ).
Commit history for context:
options.php'ping_sites'added to the optionsarray[]via [12825].options-writing.php:textarea#ping_sitesadded in [949].options-writing.php: conditionally rendering theping_sitesbased onblog_publicadded in [4326].
Evaluating the patches / approaches
The root cause is as @kitchin noted. kitchin59818.diff makes sense to me given the relationship between these 2 options and the purpose of the list of allowed 'writing' options.
While defensive guarding and consistency from 59818.diff also makes sense, the expected data type for the 'ping_sites' option is string (not nullable string).
So if the ping_sites option is not in the post back, then it shouldn't be added to the options list to sanitize in sanitize_option().
As I note, both patches resolve the issue, while IMO kitchin59818.diff resolves the root cause that is driving the error.
For 6.6, I'm in favor of moving forward with kitchin59818.diff.
@SergeyBiryukov any objections? What do you think?
Marking kitchin59818.diff for commit, but will wait until before 6.6 Beta 3 to give Sergey time to review.
#14
in reply to:
↑ 13
;
follow-up:
↓ 15
@
18 months ago
Replying to hellofromTonya:
For 6.6, I'm in favor of moving forward with kitchin59818.diff.
@SergeyBiryukov any objections? What do you think?
I agree with your assessment, thanks!
#15
in reply to:
↑ 14
@
18 months ago
- Owner changed from SergeyBiryukov to hellofromTonya
- Status changed from accepted to assigned
Replying to SergeyBiryukov:
Replying to hellofromTonya:
For 6.6, I'm in favor of moving forward with kitchin59818.diff.
@SergeyBiryukov any objections? What do you think?
I agree with your assessment, thanks!
Awesome :) Thanks @SergeyBiryukov.
I'm preparing the commit now.
This ticket was mentioned in PR #6845 on WordPress/wordpress-develop by @hellofromTonya.
18 months ago
#16
Commit confidence check to ensure `kitchin59818.diff` passes all CI jobs before committing.
Trac ticket: https://core.trac.wordpress.org/ticket/59818
@hellofromTonya commented on PR #6845:
18 months ago
#18
Committed via https://core.trac.wordpress.org/changeset/58425.


Hi there, thanks for the ticket!
59818.diff follows the approach from [56132] / #57728.