Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Widget-Visibility: Out of Memory leads to 500 Error on Several Simple Sites #56526

Closed
donalirl opened this issue Sep 24, 2021 · 20 comments
Closed
Assignees
Labels
[Pod] Flow Patrol (Manage) [Pri] High Address as soon as possible after BLOCKER issues [Type] Bug User Report This issue was created following a WordPress customer report

Comments

@donalirl
Copy link

donalirl commented Sep 24, 2021

Quick summary

Several simple sites are producing a 500 error and not loading. All sites are using a classic theme, and they load if they are switched to a newer theme.

See Also

Steps to reproduce

Unable to reproduce so far, but we have received at least 4 reports of this.

#29555158-hc (Lovecraft)
#30342258-hc (Toujours)
#4322338-zen (Colinear)
#4314833-zen (Baskerville 2)

Switching to a newer theme makes the site load. In some cases, switching back to the original classic theme works fine. Other times, the site goes down again.

Memory Errors

[Suppressed] Fatal error for [redacted] on web231.dca in /home/wpcom/public_html/wp-includes/functions.php:624 - Allowed memory size of 402653184 bytes exhausted (tried to allocate 20480 bytes) #wpcom
[Suppressed] Fatal error for [redacted] on web276.bur in /home/wpcom/public_html/wp-includes/functions.php:624 - Allowed memory size of 402653184 bytes exhausted (tried to allocate 20480 bytes) #wpcom

I have redacted the post URLs from the above error messages but they can be seen here: p1632446966171200/1632443368.154100-slack-C03TY6J1A

What you expected to happen

The sites to load.

What actually happened

The sites produced a 500 error and did not load.

Simple, Atomic or both?

Simple

Severity

Some (< 50%)

Available workarounds?

Sometimes switching the theme and then changing it back to the classic theme works, but not consistently in all cases.

@donalirl donalirl added [Type] Bug User Report This issue was created following a WordPress customer report labels Sep 24, 2021
@Greatdane
Copy link

Greatdane commented Sep 24, 2021

Another; 4322496-zd-woothemes (Lovecraft)

@erricgunawan
Copy link

Ticket: 4322096-zen
Plan: Personal
Theme: Hemingway Rewritten

Switching to another theme then back didn't work (still error).

@devNigel
Copy link

Another report: 31845088-hc

Site was using the theme Blask, tried switching to couple of other themes, did not work.

Follow up: 4322711-zen

@adelineyaw
Copy link

adelineyaw commented Sep 24, 2021

4321943-zen
Plan: Premium
Theme: Arcane

4317107-zen
Plan: Premium
Theme: Revelar

Switching to a block based theme (worked) but switching back to previous theme (ie. Arcane, Revelar) didn't work.

@devNigel
Copy link

User report: 31766471-hc
Plan: Premium
Theme: Lovecraft

Changing the theme and switching back did not work.

Helped them switch to a similar theme they selected - Twenty Eleven to avoid loss of business.

Follow-up: 4322832-zen

@jeherve
Copy link
Member

jeherve commented Sep 24, 2021

I've reverted a change we had made a few hours ago to try to solve this.

Internal references:

  • r232237-wpcom
  • p1632459711126000-slack-CBTN58FTJ
  • p7DVsv-cIY-p2#comment-37757

Potentially related to Automattic/jetpack#20731

cc @mreishus

@donalirl
Copy link
Author

@jeherve all the sites reported above are working now. Although about half of them switched themes, the ones that didn't are loading normally.

@mreishus mreishus changed the title 500 Error on Several Simple Sites Widget-Visibility: Out of Memory leads to 500 Error on Several Simple Sites Sep 24, 2021
@mreishus
Copy link
Contributor

mreishus commented Sep 24, 2021

It's definitely caused by Automattic/jetpack#20731, and we are seeing two failure states: Out of Memory and render->null(). I've turned this issue into Out Of Memory, and created #56546 for render->null().

First impressions of the Out of Memory error is that some of the errors occur on pages like sitemap.xml and ads.txt, which is not expected if the error is related to traversing large sets of blocks and some sort of interesting signal. (See: 2a4c4-pb )

@obenland
Copy link
Member

With the revert clearing these errors, can this ticket be closed?

@mreishus mreishus self-assigned this Sep 27, 2021
@mreishus
Copy link
Contributor

With the revert clearing these errors, can this ticket be closed?

Planning to use this to track the debugging of the issue; it contains relevant information and the widget visibility PR itself is getting hard to navigate.

@jeherve
Copy link
Member

jeherve commented Sep 27, 2021

@mreishus If we don't figure this out before October 5 (the day of the release of Jetpack 10.2), do you think we should revert / disable the new Widget Visibility options in Jetpack?

cc @samiff

@mreishus
Copy link
Contributor

This is caused by an infinite loop. Here is a stack trace:

[0] Jetpack_Widget_Conditions::filter_widget @ /home/wpcom/public_html/wp-content/mu-plugins/widget-conditions.php:776
[1] Jetpack_Widget_Conditions::sidebars_widgets @ /home/wpcom/public_html/wp-content/mu-plugins/widget-conditions.php:723
[2] WP_Hook->apply_filters @ /home/wpcom/public_html/wp-includes/class-wp-hook.php:303
[3] apply_filters @ /home/wpcom/public_html/wp-includes/plugin.php:189
[4] wp_get_sidebars_widgets @ /home/wpcom/public_html/wp-includes/widgets.php:1035
[5] is_active_sidebar @ /home/wpcom/public_html/wp-includes/widgets.php:971
[6] The_Neverending_Home_Page::get_settings @ /home/wpcom/public_html/wp-content/mu-plugins/infinity/infinity.php:203
[7] The_Neverending_Home_Page::archive_supports_infinity @ /home/wpcom/public_html/wp-content/mu-plugins/infinity/infinity.php:1607
[8] The_Neverending_Home_Page->posts_per_page_query @ /home/wpcom/public_html/wp-content/mu-plugins/infinity/infinity.php:751
[9] WP_Hook->apply_filters @ /home/wpcom/public_html/wp-includes/class-wp-hook.php:303
[10] WP_Hook->do_action @ /home/wpcom/public_html/wp-includes/class-wp-hook.php:327
[11] do_action_ref_array @ /home/wpcom/public_html/wp-includes/plugin.php:518
[12] WP_Query->get_posts @ /home/wpcom/public_html/wp-includes/class-wp-query.php:1784
[13] WP_Query->query @ /home/wpcom/public_html/wp-includes/class-wp-query.php:3465
[14] WP_Query->__construct @ /home/wpcom/public_html/wp-includes/class-wp-query.php:3576
[15] newspack_blocks_render_block_carousel @ /home/wpcom/public_html/wp-content/plugins/editing-toolkit-plugin/prod/newspack-blocks/synced-newspack-blocks/blocks/carousel/view.php:34
[16] {closure:/home/wpcom/public_html/wp-content/plugins/gutenberg-core/v11.5.1/lib/compat.php:94-133} @ /home/wpcom/public_html/wp-content/plugins/gutenberg-core/v11.5.1/lib/compat.php:132
[17] WP_Block->render @ /home/wpcom/public_html/wp-includes/class-wp-block.php:224
[18] render_block @ /home/wpcom/public_html/wp-includes/blocks.php:868
[19] Jetpack_Widget_Conditions::filter_widget @ /home/wpcom/public_html/wp-content/mu-plugins/widget-conditions.php:796
[20] Jetpack_Widget_Conditions::sidebars_widgets @ /home/wpcom/public_html/wp-content/mu-plugins/widget-conditions.php:723
[21] WP_Hook->apply_filters @ /home/wpcom/public_html/wp-includes/class-wp-hook.php:303
[22] apply_filters @ /home/wpcom/public_html/wp-includes/plugin.php:189
[23] wp_get_sidebars_widgets @ /home/wpcom/public_html/wp-includes/widgets.php:1035
[24] is_active_sidebar @ /home/wpcom/public_html/wp-includes/widgets.php:971
[25] The_Neverending_Home_Page::get_settings @ /home/wpcom/public_html/wp-content/mu-plugins/infinity/infinity.php:203
[26] The_Neverending_Home_Page::archive_supports_infinity @ /home/wpcom/public_html/wp-content/mu-plugins/infinity/infinity.php:1607
[27] The_Neverending_Home_Page->posts_per_page_query @ /home/wpcom/public_html/wp-content/mu-plugins/infinity/infinity.php:751
[28] WP_Hook->apply_filters @ /home/wpcom/public_html/wp-includes/class-wp-hook.php:303
[29] WP_Hook->do_action @ /home/wpcom/public_html/wp-includes/class-wp-hook.php:327
[30] do_action_ref_array @ /home/wpcom/public_html/wp-includes/plugin.php:518
[31] WP_Query->get_posts @ /home/wpcom/public_html/wp-includes/class-wp-query.php:1784
[32] WP_Query->query @ /home/wpcom/public_html/wp-includes/class-wp-query.php:3465
[33] WP_Query->__construct @ /home/wpcom/public_html/wp-includes/class-wp-query.php:3576
[34] newspack_blocks_render_block_carousel @ /home/wpcom/public_html/wp-content/plugins/editing-toolkit-plugin/prod/newspack-blocks/synced-newspack-blocks/blocks/carousel/view.php:34
[35] {closure:/home/wpcom/public_html/wp-content/plugins/gutenberg-core/v11.5.1/lib/compat.php:94-133} @ /home/wpcom/public_html/wp-content/plugins/gutenberg-core/v11.5.1/lib/compat.php:132
[36] WP_Block->render @ /home/wpcom/public_html/wp-includes/class-wp-block.php:224
[37] render_block @ /home/wpcom/public_html/wp-includes/blocks.php:868
[38] Jetpack_Widget_Conditions::filter_widget @ /home/wpcom/public_html/wp-content/mu-plugins/widget-conditions.php:796
[39] Jetpack_Widget_Conditions::sidebars_widgets @ /home/wpcom/public_html/wp-content/mu-plugins/widget-conditions.php:723
[40] WP_Hook->apply_filters @ /home/wpcom/public_html/wp-includes/class-wp-hook.php:303
[41] apply_filters @ /home/wpcom/public_html/wp-includes/plugin.php:189
[42] wp_get_sidebars_widgets @ /home/wpcom/public_html/wp-includes/widgets.php:1035
[43] is_active_sidebar @ /home/wpcom/public_html/wp-includes/widgets.php:971
[44] The_Neverending_Home_Page::get_settings @ /home/wpcom/public_html/wp-content/mu-plugins/infinity/infinity.php:203
[45] The_Neverending_Home_Page::archive_supports_infinity @ /home/wpcom/public_html/wp-content/mu-plugins/infinity/infinity.php:1607
[46] The_Neverending_Home_Page->posts_per_page_query @ /home/wpcom/public_html/wp-content/mu-plugins/infinity/infinity.php:751
[47] WP_Hook->apply_filters @ /home/wpcom/public_html/wp-includes/class-wp-hook.php:303
[48] WP_Hook->do_action @ /home/wpcom/public_html/wp-includes/class-wp-hook.php:327
[49] do_action_ref_array @ /home/wpcom/public_html/wp-includes/plugin.php:518
[50] WP_Query->get_posts @ /home/wpcom/public_html/wp-includes/class-wp-query.php:1784
[51] WP_Query->query @ /home/wpcom/public_html/wp-includes/class-wp-query.php:3465
[52] WP_Query->__construct @ /home/wpcom/public_html/wp-includes/class-wp-query.php:3576
[53] newspack_blocks_render_block_carousel @ /home/wpcom/public_html/wp-content/plugins/editing-toolkit-plugin/prod/newspack-blocks/synced-newspack-blocks/blocks/carousel/view.php:34
[54] {closure:/home/wpcom/public_html/wp-content/plugins/gutenberg-core/v11.5.1/lib/compat.php:94-133} @ /home/wpcom/public_html/wp-content/plugins/gutenberg-core/v11.5.1/lib/compat.php:132
[55] WP_Block->render @ /home/wpcom/public_html/wp-includes/class-wp-block.php:224
[56] render_block @ /home/wpcom/public_html/wp-includes/blocks.php:868
[57] Jetpack_Widget_Conditions::filter_widget @ /home/wpcom/public_html/wp-content/mu-plugins/widget-conditions.php:796
[58] Jetpack_Widget_Conditions::sidebars_widgets @ /home/wpcom/public_html/wp-content/mu-plugins/widget-conditions.php:723
[59] WP_Hook->apply_filters @ /home/wpcom/public_html/wp-includes/class-wp-hook.php:303
[60] apply_filters @ /home/wpcom/public_html/wp-includes/plugin.php:189
[61] wp_get_sidebars_widgets @ /home/wpcom/public_html/wp-includes/widgets.php:1035
[62] is_active_sidebar @ /home/wpcom/public_html/wp-includes/widgets.php:971
[63] The_Neverending_Home_Page::get_settings @ /home/wpcom/public_html/wp-content/mu-plugins/infinity/infinity.php:203
[64] The_Neverending_Home_Page::archive_supports_infinity @ /home/wpcom/public_html/wp-content/mu-plugins/infinity/infinity.php:1607
[65] The_Neverending_Home_Page->posts_per_page_query @ /home/wpcom/public_html/wp-content/mu-plugins/infinity/infinity.php:751
[66] WP_Hook->apply_filters @ /home/wpcom/public_html/wp-includes/class-wp-hook.php:303
[67] WP_Hook->do_action @ /home/wpcom/public_html/wp-includes/class-wp-hook.php:327
[68] do_action_ref_array @ /home/wpcom/public_html/wp-includes/plugin.php:518
[69] WP_Query->get_posts @ /home/wpcom/public_html/wp-includes/class-wp-query.php:1784
[70] WP_Query->query @ /home/wpcom/public_html/wp-includes/class-wp-query.php:3465
[71] WP_Query->__construct @ /home/wpcom/public_html/wp-includes/class-wp-query.php:3576
[72] newspack_blocks_render_block_carousel @ /home/wpcom/public_html/wp-content/plugins/editing-toolkit-plugin/prod/newspack-blocks/synced-newspack-blocks/blocks/carousel/view.php:34
[73] {closure:/home/wpcom/public_html/wp-content/plugins/gutenberg-core/v11.5.1/lib/compat.php:94-133} @ /home/wpcom/public_html/wp-content/plugins/gutenberg-core/v11.5.1/lib/compat.php:132
[74] WP_Block->render @ /home/wpcom/public_html/wp-includes/class-wp-block.php:224
[75] render_block @ /home/wpcom/public_html/wp-includes/blocks.php:868
[76] Jetpack_Widget_Conditions::filter_widget @ /home/wpcom/public_html/wp-content/mu-plugins/widget-conditions.php:796
[77] Jetpack_Widget_Conditions::sidebars_widgets @ /home/wpcom/public_html/wp-content/mu-plugins/widget-conditions.php:723
[78] WP_Hook->apply_filters @ /home/wpcom/public_html/wp-includes/class-wp-hook.php:303
[79] apply_filters @ /home/wpcom/public_html/wp-includes/plugin.php:189
[80] wp_get_sidebars_widgets @ /home/wpcom/public_html/wp-includes/widgets.php:1035
[81] is_active_sidebar @ /home/wpcom/public_html/wp-includes/widgets.php:971
[82] The_Neverending_Home_Page::get_settings @ /home/wpcom/public_html/wp-content/mu-plugins/infinity/infinity.php:203
[83] The_Neverending_Home_Page::archive_supports_infinity @ /home/wpcom/public_html/wp-content/mu-plugins/infinity/infinity.php:1607
[84] The_Neverending_Home_Page->posts_per_page_query @ /home/wpcom/public_html/wp-content/mu-plugins/infinity/infinity.php:751
[85] WP_Hook->apply_filters @ /home/wpcom/public_html/wp-includes/class-wp-hook.php:303
[86] WP_Hook->do_action @ /home/wpcom/public_html/wp-includes/class-wp-hook.php:327
[87] do_action_ref_array @ /home/wpcom/public_html/wp-includes/plugin.php:518
[88] WP_Query->get_posts @ /home/wpcom/public_html/wp-includes/class-wp-query.php:1784
[89] WP_Query->query @ /home/wpcom/public_html/wp-includes/class-wp-query.php:3465
[90] WP_Query->__construct @ /home/wpcom/public_html/wp-includes/class-wp-query.php:3576
[91] newspack_blocks_render_block_carousel @ /home/wpcom/public_html/wp-content/plugins/editing-toolkit-plugin/prod/newspack-blocks/synced-newspack-blocks/blocks/carousel/view.php:34
[92] {closure:/home/wpcom/public_html/wp-content/plugins/gutenberg-core/v11.5.1/lib/compat.php:94-133} @ /home/wpcom/public_html/wp-content/plugins/gutenberg-core/v11.5.1/lib/compat.php:132
[93] WP_Block->render @ /home/wpcom/public_html/wp-includes/class-wp-block.php:224
[94] render_block @ /home/wpcom/public_html/wp-includes/blocks.php:868
[95] Jetpack_Widget_Conditions::filter_widget @ /home/wpcom/public_html/wp-content/mu-plugins/widget-conditions.php:796
[96] Jetpack_Widget_Conditions::sidebars_widgets @ /home/wpcom/public_html/wp-content/mu-plugins/widget-conditions.php:723
[97] WP_Hook->apply_filters @ /home/wpcom/public_html/wp-includes/class-wp-hook.php:303
[98] apply_filters @ /home/wpcom/public_html/wp-includes/plugin.php:189
[99] wp_get_sidebars_widgets @ /home/wpcom/public_html/wp-includes/widgets.php:1035
[100] is_active_sidebar @ /home/wpcom/public_html/wp-includes/widgets.php:971
[101] The_Neverending_Home_Page::get_settings @ /home/wpcom/public_html/wp-content/mu-plugins/infinity/infinity.php:203
[102] The_Neverending_Home_Page->__construct @ /home/wpcom/public_html/wp-content/mu-plugins/infinity/infinity.php:54
[103] the_neverending_home_page_init @ /home/wpcom/public_html/wp-content/mu-plugins/infinity/infinity.php:2013
[104] WP_Hook->apply_filters @ /home/wpcom/public_html/wp-includes/class-wp-hook.php:303
[105] WP_Hook->do_action @ /home/wpcom/public_html/wp-includes/class-wp-hook.php:327
[106] do_action @ /home/wpcom/public_html/wp-includes/plugin.php:470
[107] require @ /home/wpcom/public_html/wp-settings.php:567
[108] require_once @ /home/wpcom/public_html/wp-config.php:7
[109] require_once @ /home/wpcom/public_html/wp-load.php:50
[110] require @ /home/wpcom/public_html/wp-blog-header.php:13
[111] {main} @ /home/wpcom/public_html/index.php:17

The difference is that with legacy widgets, the filter_widget() has two possible return values:

  • Returns the $instance passed to it OR
  • Returns false

Block widgets add a new possibility:

  • Change the content inside $instance, by rerendering $instance['content']

The problem here is that running render_block() has the possibility to trigger calls which run filter_widget() again. Specifically, what's happening here is related to The_Neverending_Home_Page::get_settings(). get_settings() is trying to find out if there's an active sidebar, so it calls is_active_sidebar(), which calls wp_get_sidebars_widgets(), which calls apply_filters( 'sidebars_widgets', $sidebars_widgets ); on the widgets it's returning, which calls filter_widget(), which renders its contents, which leads to The_Neverending_Home_Page::get_settings(), which goes down the chain again forever.

@obenland
Copy link
Member

Is there a way to remove the filter ahead of rendering $instance['content']?

@mreishus
Copy link
Contributor

mreishus commented Sep 27, 2021

Is there a way to remove the filter ahead of rendering $instance['content']?

This (1) works in my testing but I don't feel great about:

diff --git a/projects/plugins/jetpack/modules/widget-visibility/widget-conditions.php b/projects/plugins/jetpack/modules/widget-visibility/widget-conditions.php
index 669fedf56..cf5efdf52 100644
--- a/projects/plugins/jetpack/modules/widget-visibility/widget-conditions.php
+++ b/projects/plugins/jetpack/modules/widget-visibility/widget-conditions.php
@@ -789,11 +789,15 @@ class Jetpack_Widget_Conditions {
                        }
 
                        $output = '';
+                       remove_filter( 'widget_display_callback', array( __CLASS__, 'filter_widget' ) );
+                       remove_filter( 'sidebars_widgets', array( __CLASS__, 'sidebars_widgets' ) );
                        foreach ( $blocks as $block ) {
                                if ( ! empty( $block ) ) {
                                        $output .= render_block( $block );
                                }
                        }
+                       add_filter( 'widget_display_callback', array( __CLASS__, 'filter_widget' ) );
+                       add_filter( 'sidebars_widgets', array( __CLASS__, 'sidebars_widgets' ) );
                        $instance['content'] = $output;
 
                        return $instance;

I think it's too dependent upon the filters that we set up around this function. That is, if something else starts hooking onto this function and using it a different way, the same problem could crop up again.

This (2) also works in my testing and I feel a bit better about:

diff --git a/projects/plugins/jetpack/modules/widget-visibility/widget-conditions.php b/projects/plugins/jetpack/modules/widget-visibility/widget-conditions.php
index 669fedf56..a90dafde9 100644
--- a/projects/plugins/jetpack/modules/widget-visibility/widget-conditions.php
+++ b/projects/plugins/jetpack/modules/widget-visibility/widget-conditions.php
@@ -20,6 +20,7 @@ use Automattic\Jetpack\Assets;
  */
 class Jetpack_Widget_Conditions {
        static $passed_template_redirect = false;
+       static $is_filter_widget_rendering = false;
 
        public static function init() {
                global $pagenow;
@@ -759,6 +760,10 @@ class Jetpack_Widget_Conditions {
         * @return array Settings to display or bool false to hide.
         */
        public static function filter_widget( $instance ) {
+               if ( self::$is_filter_widget_rendering ) {
+                       return $instance;
+               }
+
                // Don't filter widgets from the REST API when it's called via the widgets admin page - otherwise they could get
                // filtered out and become impossible to edit.
                if ( strpos( wp_get_raw_referer(), '/wp-admin/widgets.php' ) && false !== strpos( $_SERVER['REQUEST_URI'], '/wp-json/' ) ) {
@@ -789,11 +794,13 @@ class Jetpack_Widget_Conditions {
                        }
 
                        $output = '';
+                       self::$is_filter_widget_rendering = true;
                        foreach ( $blocks as $block ) {
                                if ( ! empty( $block ) ) {
                                        $output .= render_block( $block );
                                }
                        }
+                       self::$is_filter_widget_rendering = false;
                        $instance['content'] = $output;
 
                        return $instance;

Another solution is to (3) rework the function so it doesn't render at all. I haven't tried this, but I think it would mean limiting the functionality. Instead of being allowed to set visibility rules on any block, anywhere in widgets (which admittedly, is pretty cool), it would only check the top level block for each widget, and either return them in their entirety or false in their place, just like the old way. I'm not 100% sure that this would work (it depends on the shape of the blocks sent to the function), but this way would limit the functionality of the feature and by removing the render call entirely, remove an entire complex area where things could go wrong.

@mreishus
Copy link
Contributor

mreishus commented Sep 27, 2021

@mreishus If we don't figure this out before October 5 (the day of the release of Jetpack 10.2), do you think we should revert / disable the new Widget Visibility options in Jetpack?

Yes.

@mreishus
Copy link
Contributor

mreishus commented Sep 27, 2021

I have a fix for this issue (render inside the visibility feature inadvertently trigger an infinite loop). However, I don't have a fix for #56546 (render inside visibility feature tries to render null and crashes). I'm also not able to reproduce that issue. Additionally, there could be more issues out there that we don't know about.

I propose scaling back the functionality offered by the feature. Instead of letting the user set visibility rules on any block inside a block widget, only allow the rules to be set on the outermost block of each block widget. In my opinion, this should cover the vast majority of user cases (I want this widget to display or not), while significantly reducing the complexity of the widget: The filter function will no longer have to change the content of blocks, and no longer have to call the render() function. Since #56546 is caused by a call to render(), if I can stop the filter from ever needing to render at all, then I can fix the issue.

The following patch works in my testing. It does need additional changes on the front-end to only display the visibility options to the user on the outermost block of each widget.

diff --git a/projects/plugins/jetpack/modules/widget-visibility/editor/index.jsx b/projects/plugins/jetpack/modules/widget-visibility/editor/index.jsx
index bf8fa4f692..20c86faff7 100644
--- a/projects/plugins/jetpack/modules/widget-visibility/editor/index.jsx
+++ b/projects/plugins/jetpack/modules/widget-visibility/editor/index.jsx
@@ -6,6 +6,7 @@ import { BaseControl, Button, SelectControl, ToggleControl } from '@wordpress/co
 import { __, _x } from '@wordpress/i18n';
 import { InspectorAdvancedControls } from '@wordpress/block-editor'; // eslint-disable-line import/no-unresolved
 import { createHigherOrderComponent } from '@wordpress/compose';
+import { useSelect } from '@wordpress/data';
 
 /* global widget_conditions_data, wpcom */
 /* eslint-disable react/react-in-jsx-scope */
@@ -223,10 +224,20 @@ const maybeAddDefaultConditions = conditions => ( {
 
 const visibilityAdvancedControls = createHigherOrderComponent(
 	BlockEdit => props => {
-		const { attributes, setAttributes, isSelected } = props;
+		const { clientId, attributes, setAttributes, isSelected } = props;
 		const conditions = useMemo( () => attributes.conditions || {}, [ attributes ] );
 		const rules = useMemo( () => conditions.rules || [], [ conditions ] );
 
+		const isParentWidgetArea = useSelect(
+			select => {
+				const { getBlockParents, getBlock } = select( 'core/block-editor' );
+				const parents = getBlockParents( clientId, true );
+				const parentBlock = parents ? getBlock( parents[ 0 ] ) : undefined;
+				return parentBlock && parentBlock.name == 'core/widget-area';
+			},
+			[ clientId ]
+		);
+
 		const toggleMatchAll = useCallback(
 			() =>
 				setAttributes( {
@@ -382,9 +393,22 @@ const visibilityAdvancedControls = createHigherOrderComponent(
 		return (
 			<Fragment>
 				<BlockEdit { ...props } />
-				{ isSelected && blockHasVisibilitySettings( props.name ) && (
+				{ isSelected && isParentWidgetArea && blockHasVisibilitySettings( props.name ) && (
 					<InspectorAdvancedControls>{ mainRender }</InspectorAdvancedControls>
 				) }
+				{ isSelected && ! isParentWidgetArea && blockHasVisibilitySettings( props.name ) && (
+					<InspectorAdvancedControls>
+						<BaseControl
+							id="widget-vis__wrapper"
+							className="widget-vis__wrapper"
+							label={ __( 'Visibility', 'jetpack' ) }
+							help={ __(
+								'Please select the top level block to apply widget visibility.',
+								'jetpack'
+							) }
+						></BaseControl>
+					</InspectorAdvancedControls>
+				) }
 			</Fragment>
 		);
 	},
diff --git a/projects/plugins/jetpack/modules/widget-visibility/widget-conditions.php b/projects/plugins/jetpack/modules/widget-visibility/widget-conditions.php
index 669fedf560..b6b413a2f9 100644
--- a/projects/plugins/jetpack/modules/widget-visibility/widget-conditions.php
+++ b/projects/plugins/jetpack/modules/widget-visibility/widget-conditions.php
@@ -781,90 +781,20 @@ class Jetpack_Widget_Conditions {
 			return false;
 		} elseif ( ! empty( $instance['content'] ) && has_blocks( $instance['content'] ) ) {
 			// Block-Based widgets: We have gutenberg blocks that could have the 'conditions' attribute
-			// Note: These blocks can be nested, and we would have to apply these conditions at any level.
 			$blocks = parse_blocks( $instance['content'] );
-			$blocks = self::recursively_filter_blocks( $blocks )[0];
-			if ( empty( $blocks ) ) {
-				return false;
+			if ( empty( $blocks[0]['attrs']['conditions']['rules'] ) ) {
+				return $instance;
 			}
-
-			$output = '';
-			foreach ( $blocks as $block ) {
-				if ( ! empty( $block ) ) {
-					$output .= render_block( $block );
-				}
+			if ( self::filter_widget_check_conditions( $blocks[0]['attrs']['conditions'] ) ) {
+				return $instance;
 			}
-			$instance['content'] = $output;
-
-			return $instance;
+			return false;
 		}
 
 		// No visibility found.
 		return $instance;
 	}
 
-	/**
-	 * Recursively check the visibility conditions in blocks' attr.conditions.rules.
-	 * Any that fail the check will be removed.
-	 *
-	 * @param array $blocks (By reference; will be modified).
-	 * @return array [$blocks, $indexes_removed]
-	 */
-	public static function recursively_filter_blocks( &$blocks ) {
-		$indexes_removed = array();
-
-		foreach ( $blocks as $i => $block ) {
-			$keep = true;
-			if ( ! empty( $block['attrs']['conditions']['rules'] ) ) {
-				$keep = self::filter_widget_check_conditions( $block['attrs']['conditions'] );
-			}
-
-			if ( ! $keep ) {
-				unset( $blocks[ $i ] );
-				array_push( $indexes_removed, $i );
-			} elseif ( ! empty( $block['innerBlocks'] ) && is_array( $block['innerBlocks'] ) ) {
-				$result                      = self::recursively_filter_blocks( $blocks[ $i ]['innerBlocks'] );
-				$blocks[ $i ]['innerBlocks'] = $result[0];
-
-				// For each block we removed in innerBlocks, we must remove a corresponding 'null' in innerContent
-				// if $sub_indexes_removed = [0, 2], we removed the 0th and 2nd innerBlocks, and need to remove
-				// the 0th and 2nd nulls in innerContent.
-				$sub_indexes_removed = $result[1];
-				if ( ! empty( $sub_indexes_removed ) ) {
-					$null_locations = array_keys( $blocks[ $i ]['innerContent'], null, true );
-					$null_count     = count( $null_locations );
-
-					// phpcs:disable Squiz.PHP.CommentedOutCode.Found
-
-					/*
-					Example:
-					null_locations = [ 1, 3, 5 ]    "Nulls exist at j=1, j=3, j=5".
-					null_count = 3.                 "There are 3 nulls".
-					sub_indexes_removed = [ 0, 2 ]. "We want to remove the 0th and 2nd null".
-					We need to remove the 0th null (at j = 1) and the 2nd null (at j = 5).
-					[ 'abc', null, 'def', null, 'ghi', null ].
-					..........^ j = 1, k = 0              ^.
-					.......................^ j = 3, k = 1 ^.
-					......................................^ j = 5, k = 2.
-					*/
-
-					// phpcs:enable Squiz.PHP.CommentedOutCode.Found
-
-					$k = $null_count - 1;
-					// Work backwards so as we delete items, then the other indexes don't change.
-					foreach ( array_reverse( $null_locations ) as $j ) {
-						// We are looking at a null, which is the $j index of the array and the $kth null in total (k is also 0 indexed).
-						if ( in_array( $k, $sub_indexes_removed, true ) ) {
-							array_splice( $blocks[ $i ]['innerContent'], $j, 1 );
-						}
-						$k--;
-					}
-				}
-			}
-		}
-		return array( $blocks, $indexes_removed );
-	}
-
 	/**
 	 * Determine whether the widget should be displayed based on conditions set by the user.
 	 *
1

Edit 9/28: Updated with front-end changes.

I'm not entirely satisfied with this solution - I thought that specifying rules at any block level was a powerful, conceptually elegant solution - but I'm leaning towards it being a necessary compromise given the complexity of WP filters, existing user setups, and limited resources we have for this project.

@mreishus
Copy link
Contributor

@jeherve We're in an odd spot development wise with JP and dotcom being unsynced. The changes are on JP, merged, but manually reverted on dotcom. I've been using svn merge --ignore-ancestry -c -232237 . and manual editing to try out changes, but I would like to get back into a workflow where I can submit changes to jetpack and have dotcom patches automatically generated that I can hand out for testing. Maybe revert on JP, create a new PR w/ the changes so far? What do you recommend?

@jeherve
Copy link
Member

jeherve commented Sep 28, 2021

As we discussed in Slack, at this point it may be easier to open a Jetpack PR, and then manually open a diff on WordPress.com Simple to bring everything over later on. We will not be able to rely on Fusion for this one unfortunately.

@mreishus
Copy link
Contributor

Created Automattic/jetpack#21222

@dpasque dpasque added the [Pri] High Address as soon as possible after BLOCKER issues label Sep 30, 2021
@mreishus
Copy link
Contributor

mreishus commented Oct 6, 2021

We've launched widget visibility, reworked a bit, so this error can no longer occur. Please re-open if you are seeing any other issues like this.

@mreishus mreishus closed this as completed Oct 6, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
[Pod] Flow Patrol (Manage) [Pri] High Address as soon as possible after BLOCKER issues [Type] Bug User Report This issue was created following a WordPress customer report
Projects
None yet
Development

No branches or pull requests

10 participants