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

Support storing blocks in sidebars #14251

Closed
wants to merge 5 commits into from
Closed

Conversation

noisysocks
Copy link
Member

@noisysocks noisysocks commented Mar 6, 2019

Closes #14182 — see this issue for a lot of useful context and terminology.

Adds the ability to store blocks in WordPress sidebars alongside widgets.

A new GET /__experimental/sidebars/<id> endpoint allows you to view a sidebar. Any blocks in the sidebar are shown as is. Any widgets in the sidebar are shown as core/legacy-block blocks which are to be added in #13511.

$ http -a admin:password GET http://localhost:9999/wp-json/__experimental/sidebars/sidebar-1
HTTP/1.1 200 OK
Access-Control-Allow-Headers: Authorization, Content-Type
Access-Control-Expose-Headers: X-WP-Total, X-WP-TotalPages
Allow: GET, POST, PUT, PATCH
Cache-Control: no-cache, must-revalidate, max-age=0
Connection: keep-alive
Content-Type: application/json; charset=UTF-8
Date: Fri, 29 Mar 2019 03:33:13 GMT
Expires: Wed, 11 Jan 1984 05:00:00 GMT
Link: <http://localhost:9999/wp-json/>; rel="https://api.w.org/"
Server: nginx/1.15.9
Transfer-Encoding: chunked
X-Content-Type-Options: nosniff
X-Powered-By: PHP/7.3.2
X-Robots-Tag: noindex

{
    "after_title": "</h2>",
    "after_widget": "</section>",
    "before_title": "<h2 class=\"widget-title\">",
    "before_widget": "<section id=\"%1$s\" class=\"widget %2$s\">",
    "class": "",
    "content": "<!-- wp:paragraph {\"backgroundColor\":\"primary\"} --><p class=\"has-background has-primary-background-color\">Hello there! How are you?</p><!-- /wp:paragraph --><!-- wp:legacy-widget {\"identifier\":\"search-2\",\"instance\":{\"title\":\"My search widget\"}} /--><!-- wp:legacy-widget {\"identifier\":\"recent-posts-2\",\"instance\":{\"title\":\"\",\"number\":5,\"show_date\":false}} /--><!-- wp:legacy-widget {\"identifier\":\"recent-comments-2\",\"instance\":{\"title\":\"\",\"number\":5}} /--><!-- wp:legacy-widget {\"identifier\":\"archives-2\",\"instance\":{\"title\":\"\",\"count\":0,\"dropdown\":0}} /--><!-- wp:legacy-widget {\"identifier\":\"categories-2\",\"instance\":{\"title\":\"\",\"count\":0,\"hierarchical\":0,\"dropdown\":0}} /--><!-- wp:legacy-widget {\"identifier\":\"meta-2\",\"instance\":{\"title\":\"\"}} /-->",
    "description": "Add widgets here to appear in your footer.",
    "id": "sidebar-1",
    "name": "Footer"
}

A new PUT /__experimental/sidebars/<id> endpoint allows you to update the blocks in a sidebar.

Blocks are stored directly in the existing sidebars_widgets site option as a serialised PHP array. We intercept wp_get_sidebars_widgets() and wp_set_sidebars_widgets() and swap out blocks for a block-widget widget which is lazily registered using wp_register_sidebar_widget().

Callers that wish to see the block data can use the new gutenberg_get_sidebars_items() and gutenberg_set_sidebars_items() methods.

This approach has some big benefits:

  • Theme compatibility: Themes that use dynamic_sidebar() will continue to work and will support blocks in sidebars appear without requiring any changes.
  • Plugin compatibility: Code that uses wp_get_sidebars_widgets() or wp_set_sidebars_widgets() will continue to work without requiring any changes.
  • WP Admin compatibility: The existing /wp-admin/widgets.php screen in WP Admin continues to work. Blocks can be re-ordered and removed, just not updated.
  • Seamless upgrades: A user's widgets will continue to work upon upgrade. Conversion from a widget to a block can happen via the frontend editor using block transforms. This aligns with how the block editor works for posts.
  • "Simple" (if you grok how widgets work 😛): Very little code is required as we are using the existing widgets infrastructure in WordPress.

When merged into WordPress Core, this approach will be a little simpler to implement as we can modify wp_get_sidebars_widgets() and wp_set_sidebars_widgets() directly instead of abusing filters.

Outstanding questions

  1. How should blocks look when sent down via the API? Should we send down objects containing name, attributes and innerBlocks? Or should we send down HTML containing block delimiters so that the /sidebar endpoint matches the /posts and /blocks endpoints? Going to go with HTML, at least to start. See Support storing blocks in sidebars #14251 (comment).
  2. Does fetching and updating one sidebar at a time with GET /sidebars/<id> and PUT /sidebars/<id> make sense? Will the new widgets screen display an editor for all sidebars at the same time? Should we instead support viewing and updating multiple sidebars at once? Let's mark the API as experimental and iterate on this. See Support storing blocks in sidebars #14251 (comment).
  3. How will conversion from core/legacy-widget to e.g. core/search work on the frontend? Are we sending down enough data for this to happen? This depends a lot what happens with Try Legacy widget block #13511. Conversion will happen on the frontend. See Support storing blocks in sidebars #14251 (comment).

Outstanding tasks

  • Handle the instance attribute on a core/legacy-widget block
  • Use HTML to represent widgets in the API
  • Handle case where legacy widget is used as an inner block
  • Test with real third party widgets
  • Implement get_schema()
  • Add inline PHPDoc documentation
  • Unit tests for the new filters, methods, and REST API endpoint

@noisysocks noisysocks added [Type] Enhancement A suggestion for improvement. [Feature] Widgets Screen The block-based screen that replaced widgets.php. labels Mar 6, 2019
@noisysocks
Copy link
Member Author

cc. @WordPress/gutenberg-core @draganescu

Copy link
Member

@jorgefilipecosta jorgefilipecosta left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @noisysocks nice work here 👍
I am able to request the GET endpoint with success:

wp.apiFetch({
    path: '/wp/v2/sidebars/sidebar-1',
    method: 'GET',
}).then(console.log);

I am able to apply widget reorders and removals:

wp.apiFetch({
    path: '/wp/v2/sidebars/sidebar-1',
    data: {
        "blocks": [{
            "name": "core/legacy-widget",
            "attributes": {
                "identifier": "search-2"
            },
            "innerBlocks": [],
            "innerHTML": "",
            "innerContent": []
        }, {
            "name": "core/legacy-widget",
            "attributes": {
                "identifier": "nav_menu-2"
            },
            "innerBlocks": [],
            "innerHTML": "",
            "innerContent": []
        }]
    },
    method: 'POST',
}).then(console.log);

The changes get reflected on the widget screen 👍

When I try to add blocks using this code:

wp.apiFetch({
    path: '/wp/v2/sidebars/sidebar-1',
    data: {
        "blocks": [{
            "clientId": "0875de4f-2320-4b6b-bd69-40b048fa43ef",
            "name": "core/paragraph",
            "isValid": true,
            "attributes": {
                "content": "Hello",
                "dropCap": false
            },
            innerHTML: "<p>Hello</p>",
            innerContent: "<p>Hello</p>",
            "innerBlocks": []
        }, {
            "clientId": "cedd2160-3cb8-4c39-b293-8453e7ec74ef",
            "name": "core/paragraph",
            "isValid": true,
            "attributes": {
                "content": "World",
                "dropCap": false
            },
            innerHTML: "<p>World</p>",
            innerContent: "<p>World</p>",
            "innerBlocks": []
        }, {
            "name": "core/legacy-widget",
            "attributes": {
                "identifier": "calendar-4"
            },
            "innerBlocks": [],
            "innerHTML": "",
            "innerContent": []
        }, {
            "name": "core/legacy-widget",
            "attributes": {
                "identifier": "media_image-3"
            },
            "innerBlocks": [],
            "innerHTML": "",
            "innerContent": []
        }, {
            "name": "core/legacy-widget",
            "attributes": {
                "identifier": "calendar-3"
            },
            "innerBlocks": [],
            "innerHTML": "",
            "innerContent": []
        }, {
            "name": "core/legacy-widget",
            "attributes": {
                "identifier": "recent-posts-2"
            },
            "innerBlocks": [],
            "innerHTML": "",
            "innerContent": []
        }, {
            "name": "core/legacy-widget",
            "attributes": {
                "identifier": "search-2"
            },
            "innerBlocks": [],
            "innerHTML": "",
            "innerContent": []
        }, {
            "name": "core/legacy-widget",
            "attributes": {
                "identifier": "nav_menu-2"
            },
            "innerBlocks": [],
            "innerHTML": "",
            "innerContent": []
        }]
    },
    method: 'POST',
}).then(console.log);

The blocks get ignored and just the legacy widgets get saved. Am I doing something wrong?

} else {
$blocks[] = array(
'name' => 'core/legacy-widget',
'attributes' => array( 'identifier' => $item ),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it be possible to add the other instance attributes of the widget here?

global $wp_registered_widgets;

foreach ( $sidebars_widgets as $sidebar_id => $widgets ) {
foreach ( $widgets as $index => $widget_id ) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This line is throwing a PHP warning on my test environment when I go to the widgets page:

<b>Warning</b>:  Invalid argument supplied for foreach() in <b>/Users/pc/dev/core/wordpres-develop/build/wp-content/plugins/gutenberg/lib/register.php</b> on line <b>262</b><br />

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, I'm not getting that. Could you var_dump( $widgets ) for me and tell me what it is? I don't want to blindly put a if ( is_array( ... ) ) in here and miss a potential bug.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now I'm getting error Warning: Invalid argument supplied for foreach() in /Users/pc/dev/core/wordpres-develop/build/wp-content/plugins/gutenberg/lib/register.php on line 62
.

The contents of var_dump are:


  | array(17) {
-- | --
  | [0]=>
  | string(8) "search-2"
  | [1]=>
  | string(10) "nav_menu-2"
  | [2]=>
  | string(17) "my-marquee-widget"
  | [3]=>
  | string(18) "my-marquee-widget2"
  | [4]=>
  | string(10) "calendar-3"
  | [5]=>
  | string(10) "calendar-4"
  | [6]=>
  | string(13) "media_image-3"
  | [7]=>
  | string(14) "recent-posts-2"
  | [8]=>
  | string(10) "archives-2"
  | [9]=>
  | string(15) "media_gallery-2"
  | [10]=>
  | string(17) "recent-comments-2"
  | [11]=>
  | string(12) "categories-2"
  | [12]=>
  | string(13) "custom_html-2"
  | [13]=>
  | string(6) "text-2"
  | [14]=>
  | string(6) "meta-2"
  | [15]=>
  | string(10) "calendar-2"
  | [16]=>
  | string(13) "media_image-2"
  | }
  | array(1) {
  | [0]=>
  | string(45) "block-widget-12a002c7d5f52fd5b1030df9a1f5fcb4"
  | }
  | int(3)

Checking the sidebars_widgets options saved in the database it is possible to see that besides wp_inactive_widgets and sidebar-1 it contains [array_version] => 3 maybe this is the reason for this error.

}
}

if ( ! empty( $items ) ) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What if the user the user removes all the widgets?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch! Fixed in 678f65c.

}

if ( 'core/legacy-widget' === $block['name'] ) {
$items[] = $block['attributes']['identifier'];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What happens if the user is using a widget for the first time? Should we create a new widget instance before referencing it?

Copy link
Member Author

@noisysocks noisysocks Mar 28, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, this isn't quite correct. I think it speaks to my comment below:

In this PR I assumed that core/legacy-widget is responsible for updating the original widget. Your comment in https://github.com/WordPress/gutenberg/pull/13511/files#r261867151 has made me realise that this isn't correct, though.

I'll need to do some experimenting, but I am thinking that, when a legacy widget is updated, we store the updates in the core/legacy-widget block. This should be backwards compatible because rendering a core/legacy-widget delegates rendering to the_widget. It does mean that plugins that rely on directly reading a widget's options will stop working, though. We will also need to find a solution for updating callback widgets.

$items[] = array(
'blockName' => $block['name'],
'attrs' => $block['attributes'],
'innerBlocks' => $block['innerBlocks'],
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

innerBlocks may also contain legacy widgets, right? For example, a user may add a columns block to put two widgets side by side. Should we recursively apply the legacy widget block logic to the innerBlocks?

@jorgefilipecosta
Copy link
Member

How will conversion from core/legacy-widget to e.g. core/search work on the frontend? Are we sending down enough data for this to happen? This depends a lot what happens with #13511.

As long as we have an identifier of the widget and its instance attributes I think the conversion is possible.

Another question is if this conversion is automatic or not. E.g: If I have a search widget will I get automatically get a core/heading + core/search or will I get a core/legacy-widget that I can convert to core/search after?

If after the transformation, I change core/search block how the operation is saved? I guess the widget is removed and we use the normal process to save blocks.

If I change a legacy widget, do we remove the widget that existed and start using a normal block the "legacy-widget" as we do for migrated blocks? Or do we keep a reference in the block to the original widget and for this case we update the original widget?

@noisysocks
Copy link
Member Author

noisysocks commented Mar 11, 2019

The blocks get ignored and just the legacy widgets get saved. Am I doing something wrong?

Hey @jorgefilipecosta, try using PUT. The idea here is that since a 'sidebar' is a single resource, we use PUT to update it.

Another question is if this conversion is automatic or not. E.g: If I have a search widget will I get automatically get a core/heading + core/search or will I get a core/legacy-widget that I can convert to core/search after?

I am leaning strongly towards the latter approach. That is, users that upgrade will see core/legacy-widgets in the block editor and use the block transformation UI to transform the core/legacy-widget into a core/heading + core/search block.

This matches what we do for posts with the core/classic block. It also ensures that we don't cause a user's site to suddenly render differently when they upgrade.

You can imagine scenarios where e.g. a theme might render a text widget differently to a text block. I think that it's important that the user has a chance to confirm that changes made to the site's frontend look correct.

If after the transformation, I change core/search block how the operation is saved? I guess the widget is removed and we use the normal process to save blocks.

Yes, I think you have it. The changes will be saved into the core/search block's attributes. The old widget will no longer be in the sidebar.

If I change a legacy widget, do we remove the widget that existed and start using a normal block the "legacy-widget" as we do for migrated blocks? Or do we keep a reference in the block to the original widget and for this case we update the original widget?

That's a really good question.

In this PR I assumed that core/legacy-widget is responsible for updating the original widget. Your comment in https://github.com/WordPress/gutenberg/pull/13511/files#r261867151 has made me realise that this isn't correct, though.

I'll need to do some experimenting, but I am thinking that, when a legacy widget is updated, we store the updates in the core/legacy-widget block. This should be backwards compatible because rendering a core/legacy-widget delegates rendering to the_widget. It does mean that plugins that rely on directly reading a widget's options will stop working, though. We will also need to find a solution for updating callback widgets.

@jorgefilipecosta
Copy link
Member

Hey @jorgefilipecosta, try using PUT. The idea here is that since a 'sidebar' is a single resource, we use PUT to update it.

Hi @noisysocks,
I also tried PUT, but it still did not save blocks:
Maybe this request is wrong?

wp.apiFetch({
    path: '/wp/v2/sidebars/sidebar-1',
    data: {
        "blocks": [{
            "clientId": "0875de4f-2320-4b6b-bd69-40b048fa43ef",
            "name": "core/paragraph",
            "isValid": true,
            "attributes": {
                "content": "Hello",
                "dropCap": false
            },
            innerHTML: "<p>Hello</p>",
            innerContent: "<p>Hello</p>",
            "innerBlocks": []
        }, {
            "clientId": "cedd2160-3cb8-4c39-b293-8453e7ec74ef",
            "name": "core/paragraph",
            "isValid": true,
            "attributes": {
                "content": "World",
                "dropCap": false
            },
            innerHTML: "<p>World</p>",
            innerContent: "<p>World</p>",
            "innerBlocks": []
        }, {
            "name": "core/legacy-widget",
            "attributes": {
                "identifier": "calendar-4"
            },
            "innerBlocks": [],
            "innerHTML": "",
            "innerContent": []
        }, {
            "name": "core/legacy-widget",
            "attributes": {
                "identifier": "media_image-3"
            },
            "innerBlocks": [],
            "innerHTML": "",
            "innerContent": []
        }, {
            "name": "core/legacy-widget",
            "attributes": {
                "identifier": "calendar-3"
            },
            "innerBlocks": [],
            "innerHTML": "",
            "innerContent": []
        }, {
            "name": "core/legacy-widget",
            "attributes": {
                "identifier": "recent-posts-2"
            },
            "innerBlocks": [],
            "innerHTML": "",
            "innerContent": []
        }, {
            "name": "core/legacy-widget",
            "attributes": {
                "identifier": "search-2"
            },
            "innerBlocks": [],
            "innerHTML": "",
            "innerContent": []
        }, {
            "name": "core/legacy-widget",
            "attributes": {
                "identifier": "nav_menu-2"
            },
            "innerBlocks": [],
            "innerHTML": "",
            "innerContent": []
        }]
    },
    method: 'PUT',
}).then(console.log);

It updates the widgets referenced in core/legacy-widget thougth.

We will also need to find a solution for updating callback widgets.

In PR #14395 I'm proposing a solution to this type of widgets.

@noisysocks
Copy link
Member Author

noisysocks commented Mar 28, 2019

@jorgefilipecosta: It's because innerContent in your request is a string (innerContent: "<p>Hello</p>"). The API expects an array.

Gotchas like this reinforce how cumbersome it is having to send innerContent, innerHTML and innerBlocks around. I am beginning to think that we should represent blocks as serialised HTML in this API, since HTML is the "public interface" of a Gutenberg block.

@noisysocks
Copy link
Member Author

@jorgefilipecosta @youknowriad: Any thoughts on the Outstanding questions I have in this PR's description? 🙂

@youknowriad
Copy link
Contributor

Quick replies:

  1. I might prefer to go with the HTML version as it avoid us having to think about what is a block object in the API (is it the fully parsed object or just the attributes that comes from JSON ...).

  2. The screen will show all the areas but I don't really know if we'll have a single save button or a "save on change" behavior. I think we can address later.

  3. I'll defer to @jorgefilipecosta but I don't see any particular issue.


Can we make the endpoint "experimental" somehow or "internal", I suspect will need a few iterations so we can't get everything right on the first iteration.

Landing this #14612 will help moving forward with this PR and trying it in context.

@noisysocks
Copy link
Member Author

noisysocks commented Mar 28, 2019

Thanks Riad!

Can we make the endpoint "experimental" somehow or "internal", I suspect will need a few iterations so we can't get everything right on the first iteration.

We don't have feature flagging in PHP but maybe we could use/gutenberg/unstable or /wp/unstable as the API prefix for this REST endpoint. (And the one introduced in #13511, too.)

edit: Settled on /wp-json/__experimental/sidebars/<id>

Copy link
Member

@jorgefilipecosta jorgefilipecosta left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @noisysocks, I like the usage of content strings as the format for this endpoint it threats each sidebar as a post and makes the API's consistent.

What you think of simplifying the store mechanism and during the save merely save all the content as the instance attribute a "block widget" that is the only widget referenced in the sidebar? If the sidebar already references a block widget, we use it if not (the first time the user is changing the sidebar with Gutenberg) we create a new instance of the block widget with the sidebar content, or with just an id of a post where the post with the blocks is stored and we move all the widgets the sidebar had to inactive widgets.
cc: @aduth, @youknowriad as If I'm not in error one of you proposed something similar.

If we prefer to not go for this "simplification", we need to add a way to create instances for widgets so we can use widgets previously not used or deleted in the legacy widget block.

The updates seemed to work on my tests.
Now I managed to update the sidebar with:

wp.apiFetch({
    path: '/__experimental/sidebars/sidebar-1',
    data: { content: '<!-- wp:paragraph --><p>fghgfhfg</p><!-- /wp:paragraph --><!-- wp:paragraph --><p>fghgfhfg</p><!-- /wp:paragraph -->' },
    method: 'POST',
})

I found a problem for nested blocks the inner content is not included in the content.
E.g.: if we execute the following command it is possible to see the content is not complete:

wp.apiFetch({
    path: '/__experimental/sidebars/sidebar-1',
    data: { content: '<!-- wp:columns --><div class="wp-block-columns has-2-columns"><!-- wp:column --><div class="wp-block-column"><!-- wp:paragraph --><p>a</p><!-- /wp:paragraph --></div><!-- /wp:column --><!-- wp:column --><div class="wp-block-column"><!-- wp:paragraph --><p>brdtertertreterewrew45</p><!-- /wp:paragraph --></div><!-- /wp:column --></div><!-- /wp:columns -->' },
    method: 'POST',
}).then(console.log);

}

public function get_item( $request ) {
return rest_ensure_response( $this->get_sidebar_data( $request['id'] ) );
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I'm not in error the good practice to access attributes from a request is using the $request->get_param function e.g:$request->get_param( 'id' ).

die( 'Silence is golden.' );
}

function gutenberg_output_block_widget( $options, $block ) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this logic be on widgets.php file?

$blocks[] = array(
'blockName' => 'core/legacy-widget',
'attrs' => array(
'identifier' => $item,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the widget has a class legacy widgets reference the widget by the class name and not by the id. We may add a special condition on legacy widgets block and reference existing class id widgets by their id if you think it is worth it, it would allow us to update existing widgets via legacy widget block and still save the widget in the same entry as before.
We also need some flags e.g: to specify if a widget is a callback widget or not.

}
}

gutenberg_set_sidebars_items( array_merge(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We are saving the blocks as PHP serialization of the parsed blocks result. Maybe we should save the blocks as an HTML string using our serialization format as we do in all other places.

}

if (
'core/legacy-widget' === $block['blockName'] &&
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What if the user used the legacy widgets block, to use a widget that was never used before, and does not has any instance yet?

global $wp_registered_widgets;

foreach ( $sidebars_widgets as $sidebar_id => $widgets ) {
foreach ( $widgets as $index => $widget_id ) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now I'm getting error Warning: Invalid argument supplied for foreach() in /Users/pc/dev/core/wordpres-develop/build/wp-content/plugins/gutenberg/lib/register.php on line 62
.

The contents of var_dump are:


  | array(17) {
-- | --
  | [0]=>
  | string(8) "search-2"
  | [1]=>
  | string(10) "nav_menu-2"
  | [2]=>
  | string(17) "my-marquee-widget"
  | [3]=>
  | string(18) "my-marquee-widget2"
  | [4]=>
  | string(10) "calendar-3"
  | [5]=>
  | string(10) "calendar-4"
  | [6]=>
  | string(13) "media_image-3"
  | [7]=>
  | string(14) "recent-posts-2"
  | [8]=>
  | string(10) "archives-2"
  | [9]=>
  | string(15) "media_gallery-2"
  | [10]=>
  | string(17) "recent-comments-2"
  | [11]=>
  | string(12) "categories-2"
  | [12]=>
  | string(13) "custom_html-2"
  | [13]=>
  | string(6) "text-2"
  | [14]=>
  | string(6) "meta-2"
  | [15]=>
  | string(10) "calendar-2"
  | [16]=>
  | string(13) "media_image-2"
  | }
  | array(1) {
  | [0]=>
  | string(45) "block-widget-12a002c7d5f52fd5b1030df9a1f5fcb4"
  | }
  | int(3)

Checking the sidebars_widgets options saved in the database it is possible to see that besides wp_inactive_widgets and sidebar-1 it contains [array_version] => 3 maybe this is the reason for this error.

@spacedmonkey
Copy link
Member

Reporting from #4770 (comment) as it is related.

@noisysocks
Copy link
Member Author

Thanks all (especially @jorgefilipecosta) for the feedback here. I'm hitting the pause button on this exploration work for a little bit while I focus on writing an RFC which describes the approach I'd like us to take. Stay tuned!

@spacedmonkey
Copy link
Member

If it is at all helpful, I have been thinking over this matter a lot. I believe we should do the following.

On some future WordPress upgrade, say 5.4, run a migration script. This will take all the existing widgets stored in options and migrate them to either a reusable block or new post type "wp_widget". We can map attributes on core widgets easily to blocks. We can even take a copy of the existing data and have it as a piece of post meta, so no data is lost. We can even add a filter or action, so plugin / theme developers can do something custom on this migration if they so wish.

All widget area assignment can be handled by a new taxonomy, and new terms for created on this migration and all the new widget posts can be assigned to these terms. This means api calls can be filtered by taxonomy to get each widget area (sidebar).

This plan would need this migration script to run on upgrade and a feature flag returned, to use the old widget edit screen until this migration is complete.

IMO, I believe gutenberg should keep block in post content for now. It means that we can use existing post / term apis for widget / sidebar implementation. It load the effort onto the backend. It means no new widgets api is required, as everything will be a post. This is an idea that is been floating around for a long time. See 35669

Let me know when you want feedback @noisysocks

- Allows blocks to be stored in a WordPress sidebar along with widgets.
- Adds a REST endpoint for viewing sidebars and their blocks. Any
  widgets in the sidebar are shown as core/core-legacy blocks.
- Adds a REST endpoint for updating a sidebar's blocks. Any
  core/legacy-widget blocks are stored in a backwards compatible way.
- Block data is serialized directly as an array into the
  'sidebars_widgets' site option.
- We intercept calls to wp_get_sidebars_widgets() and replace any blocks
  with a legacy callback widget. This allows blocks to be rendered via
  dynamic_sidebar(), and ensures that existing code that uses
  wp_get_sidebars_widgets() does not break.
Update the legacy widget when an `instance` array is received, and send
down the `instance` array when possible.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
[Feature] Widgets Screen The block-based screen that replaced widgets.php. [Type] Enhancement A suggestion for improvement.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Create an API for adding blocks to widget sidebars
4 participants