Make WordPress Core

Opened 15 months ago

Last modified 6 weeks ago

#57469 reopened defect (bug)

retrieve_widgets(): fatal error when a sidebar's widgets set to null (array is expected)

Reported by: kesselb's profile kesselb Owned by:
Milestone: Priority: normal
Severity: normal Version: 4.9
Component: Widgets Keywords: php80 close
Focuses: Cc:

Description (last modified by hellofromTonya)

When changing from PHP 7.4 to PHP 8.0 it's not possible to activate our theme.

Fatal error: Uncaught Error: array_merge(): Argument #3 must be of type array, null given in wp-includes/widgets.php on line 1342
array_merge()
wp-includes/widgets.php:1342

retrieve_widgets()
wp-includes/widgets.php:1287

_wp_sidebars_changed()
wp-includes/class-wp-hook.php:310

WP_Hook::apply_filters()
wp-includes/class-wp-hook.php:332

WP_Hook::do_action()
wp-includes/plugin.php:517

do_action()
wp-includes/theme.php:3395

check_theme_switched()
wp-includes/class-wp-hook.php:308

WP_Hook::apply_filters()
wp-includes/class-wp-hook.php:332

WP_Hook::do_action()
wp-includes/plugin.php:517

do_action()
wp-settings.php:617

require_once()
wp-config.php:103

require_once()
wp-load.php:50

require_once()
wp-admin/admin.php:34

require_once()
wp-admin/plugins.php:10

Line 1342:

<?php

$shown_widgets = array_merge( ...array_values( $sidebars_widgets ) );

$sidebars_widgets

array (
  'wp_inactive_widgets' => 
  array (
  ),
  'sidebar-standard' => 
  array (
  ),
  'sidebar-marken' => NULL,
  'sidebar-typen' => NULL,
  'sidebar-test' => NULL,
  'sidebar-zubehoer' => NULL,
  'sidebar-ratgeber' => NULL,
)

PHP 8.0+ throws a type error when a non-array value is given to array_merge.

I guess NULL is not a valid value for the sidebar widgets.
Maybe our theme wrote this weird state to the options field.

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

Attachments (2)

Screenshot from 2023-01-15 21-45-32.png (173.2 KB) - added by kesselb 15 months ago.
Screenshot from 2023-01-15 21-46-30.png (77.4 KB) - added by kesselb 15 months ago.

Download all attachments as: .zip

Change History (14)

This ticket was mentioned in PR #3848 on WordPress/wordpress-develop by kesselb.


15 months ago
#1

  • Keywords has-patch has-unit-tests added

wp_map_sidebar_widgets is called in retrieve_widgets and is expected to return array<string, array>.

Before this change it was possible to return something like ['sidebar-1' => [], 'sidebar-2' => null].

array_merge in PHP 8.0 is stricter and throws an TypeError when null given.

PHP 7.4: array_merge(...[['hello', 'world'], null]) => null
PHP 8.0: array_merge(...[['hello', 'world'], null]) => Fatal error

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

#2 @audrasjb
15 months ago

  • Keywords php8 added
  • Milestone changed from Awaiting Review to 6.2
  • Version 6.1.1 deleted

Thanks for the ticket, the patch and the unit tests!

I left a comment in the PR.
Moving for 6.2 consideration.

#3 follow-up: @jrf
15 months ago

  • Keywords close added
  • Milestone 6.2 deleted

@kesselb Thanks for the ticket.

To be honest, this sounds like user error and something which should be fixed in the theme, not in WP Core.

If anything, a _doing_it_wrong should be thrown when receiving invalid data (as is the case with your use-case).

#4 @kesselb
15 months ago

Hi,

If anything, a _doing_it_wrong should be thrown when receiving invalid data (as is the case with your use-case).

Sounds good ;)

<?php
function check_sidebars_widgets_for_invalid_values($value)
{
        if (is_array($value)) {
                $value = array_filter($value, static function ($var) {
                        return $var !== null;
                });
        }
        return $value;
}

/**
 * Upgrade to PHP 8 fails on some pages with an uncaught error:
 *
 * array_merge(): Argument #3 must be of type array, null given in wp-includes/widgets.php on line 1342
 *
 * Starting with PHP 8 array_merge is stricter about null values.
 * This filter will remove sidebars with null as value.
 */
add_filter('option_sidebars_widgets', 'check_sidebars_widgets_for_invalid_values', 1, 1);

If anyone has a similar issue. The above filter prevent the fatal error.

#5 @hellofromTonya
9 months ago

  • Description modified (diff)
  • Keywords php80 added; php8 has-patch has-unit-tests removed
  • Summary changed from Uncaught Error: array_merge(): Argument #3 must be of type array, null given in wp-includes/widgets.php on line 1342 to retrieve_widgets(): fatal error when a sidebar's widgets set to null (array is expected)
  • Version set to 4.9

Changes in the ticket:

  • Updated the Summary to better reflect the issue being reported, i.e. for discoverability.
  • Updated the Version to 4.9.
  • Wrapped the PHP error in the Description.
  • Reset the keywords as the PR was closed.
  • Changed keyword to php80 to identify which PHP version increased the error's severity.

Error across PHP versions:
If null or a non-array is set for the widgets of a sidebar, then PHP throws an error (see it in action https://3v4l.org/i45nZ):

  • < 7.3: Warning: array_merge(): Argument #3 is not an array
  • PHP 7.3 to 7.4: Warning: array_merge(): Expected parameter 3 to be an array, null given
  • PHP 8+: Fatal error: Uncaught TypeError: array_merge(): Argument #3 must be of type array, null given.

Historical context:

  • [41555] / #39693 (during WP 4.9):
    • Introduced wp_map_sidebars_widgets().
    • retrieve_widgets():
      • Invoked wp_map_sidebars_widgets() to map $sidebars_widgets
      • Added $shown_widgets = call_user_func_array( 'array_merge', $sidebars_widgets );.
  • [48839] / #50913 (during WP 5.6):
    • retrieve_widgets(): changed call_user_func_array( 'array_merge', $sidebars_widgets ); to array_merge( ...array_values( $sidebars_widgets ) );.

#6 in reply to: ↑ 3 @hellofromTonya
9 months ago

  • Resolution set to invalid
  • Status changed from new to closed

Replying to jrf:

To be honest, this sounds like user error and something which should be fixed in the theme, not in WP Core.

I agree. The error flagging null as the wrong data type and informing it should an array has been present for a long time. This is a case of doing it wrong.

I'm all for defensive code to protect Core's source code. However, sometimes a native PHP error is appropriate.

When changing from PHP 7.4 to PHP 8.0 it's not possible to activate our theme.

I think in this case, not being able to activate a theme is not severe enough to warrant adding defensive guard and _doing_it_wrong().

A user using the theme with the issue upgrading to PHP 8+ would experience a fatal error, which would require manual action to restore the site. Changing Core's code for this scenario is dependent upon the frequency of this scenario happening. But I'm not seeing other reports to null for widgets causing a fatal error.

Thus, I tend to agree with @jrf to close this ticket as something to be fixed in the theme.

#7 @hellofromTonya
9 months ago

#58351 was marked as a duplicate.

#8 @hellofromTonya
9 months ago

#58351 reported the same issue but happened after switching themes:

In some subsites, that after switched themes I get the following PHP Fatal error.

@lenasterg As noted in this ticket, the root cause seems to be a problem in the theme. Can you confirm if switching back to the original theme or to a different theme resolved the issue?

#9 @janthiel
3 months ago

Hey there, just came across this issue on a recent WordPress 6.4.2 installation switching a working site with the "Enfold" Theme to "Twenty Twenty-Four" Theme.

Uncaught exception (TypeError): “array_merge(): Argument #3 must be of type array, null given“ at `./wp-includes/widgets.php:1346`.

If this is a "User Error" I am not aware of any obvious one. The site was working perfectly fine before.
We switched back to Enfold using WP-CLI but the site remains broken with the same error.

#10 @janthiel
3 months ago

Deleting the "theme_mods_twentytwentyfour" option from the DB made the site useable again.

This was the content of that option:

a:3:{i:0;b:0;s:18:"nav_menu_locations";a:0:{}s:19:"wp_classic_sidebars";a:6:{s:13:"av_everywhere";a:11:{s:4:"name";s:20:"Displayed Everywhere";s:2:"id";s:13:"av_everywhere";s:11:"description";s:0:"";s:5:"class";s:0:"";s:13:"before_widget";s:48:"<section id="%1$s" class="widget clearfix %2$s">";s:12:"after_widget";s:59:"<span class="seperator extralight-border"></span></section>";s:12:"before_title";s:24:"<h3 class="widgettitle">";s:11:"after_title";s:5:"</h3>";s:14:"before_sidebar";s:0:"";s:13:"after_sidebar";s:0:"";s:12:"show_in_rest";b:0;}s:7:"av_blog";a:11:{s:4:"name";s:12:"Sidebar Blog";s:2:"id";s:7:"av_blog";s:11:"description";s:0:"";s:5:"class";s:0:"";s:13:"before_widget";s:48:"<section id="%1$s" class="widget clearfix %2$s">";s:12:"after_widget";s:59:"<span class="seperator extralight-border"></span></section>";s:12:"before_title";s:24:"<h3 class="widgettitle">";s:11:"after_title";s:5:"</h3>";s:14:"before_sidebar";s:0:"";s:13:"after_sidebar";s:0:"";s:12:"show_in_rest";b:0;}s:8:"av_pages";a:11:{s:4:"name";s:13:"Sidebar Pages";s:2:"id";s:8:"av_pages";s:11:"description";s:0:"";s:5:"class";s:0:"";s:13:"before_widget";s:48:"<section id="%1$s" class="widget clearfix %2$s">";s:12:"after_widget";s:59:"<span class="seperator extralight-border"></span></section>";s:12:"before_title";s:24:"<h3 class="widgettitle">";s:11:"after_title";s:5:"</h3>";s:14:"before_sidebar";s:0:"";s:13:"after_sidebar";s:0:"";s:12:"show_in_rest";b:0;}s:11:"av_footer_1";a:11:{s:4:"name";s:17:"Footer - Column 1";s:2:"id";s:11:"av_footer_1";s:11:"description";s:0:"";s:5:"class";s:0:"";s:13:"before_widget";s:48:"<section id="%1$s" class="widget clearfix %2$s">";s:12:"after_widget";s:59:"<span class="seperator extralight-border"></span></section>";s:12:"before_title";s:24:"<h3 class="widgettitle">";s:11:"after_title";s:5:"</h3>";s:14:"before_sidebar";s:0:"";s:13:"after_sidebar";s:0:"";s:12:"show_in_rest";b:0;}s:11:"av_footer_2";a:11:{s:4:"name";s:17:"Footer - Column 2";s:2:"id";s:11:"av_footer_2";s:11:"description";s:0:"";s:5:"class";s:0:"";s:13:"before_widget";s:48:"<section id="%1$s" class="widget clearfix %2$s">";s:12:"after_widget";s:59:"<span class="seperator extralight-border"></span></section>";s:12:"before_title";s:24:"<h3 class="widgettitle">";s:11:"after_title";s:5:"</h3>";s:14:"before_sidebar";s:0:"";s:13:"after_sidebar";s:0:"";s:12:"show_in_rest";b:0;}s:11:"av_footer_3";a:11:{s:4:"name";s:17:"Footer - Column 3";s:2:"id";s:11:"av_footer_3";s:11:"description";s:0:"";s:5:"class";s:0:"";s:13:"before_widget";s:48:"<section id="%1$s" class="widget clearfix %2$s">";s:12:"after_widget";s:59:"<span class="seperator extralight-border"></span></section>";s:12:"before_title";s:24:"<h3 class="widgettitle">";s:11:"after_title";s:5:"</h3>";s:14:"before_sidebar";s:0:"";s:13:"after_sidebar";s:0:"";s:12:"show_in_rest";b:0;}}}

#11 @ikriv
2 months ago

  • Resolution invalid deleted
  • Status changed from closed to reopened

I was sitting on an old theme for a few years, and I hit this bug. I would see a TypeError exception in array_merge() any time I open the 'widgets' page, or after I have upgraded to a new theme.

This seems to be caused by corrupted widgets information in the database. An investigation showed that $sidebars_widgets array contains a NULL item with key 'subsidiary'.
I.e. it looked like this:

(
    [wp_inactive_widgets] => Array(...)
    [primary] => Array(...)
    [subsidiary] => NULL
)

Since $sitebars_widgets is a global variable, it is hard to tell who and why was adding a NULL element. Anyway, the problem goes away if I add the following to retrieve_widgets() in wp-incudes/widgets.php just before the call to _wp_remove_unregistered_widgets():

	// remove any NULL elements
	$sidebars_widgets = array_filter($sidebars_widgets);

I suggest to reopen this bug and add this filter to the production version: corrupted databases happen, and it's an easy precaution.

Last edited 2 months ago by ikriv (previous) (diff)

#12 @bartnv
6 weeks ago

Just a quick confirmation: I had this problem too and couldn't find the source. The line of code @ikriv suggests above worked to solve it for me.

Note: See TracTickets for help on using tickets.