-
Notifications
You must be signed in to change notification settings - Fork 4.3k
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
Add reference widgets to legacy widget blocks. Use ajax admin endpoint on this blocks. #15801
Conversation
24127bd
to
f63259e
Compare
Is it still a WIP? |
f63259e
to
89c7546
Compare
It suffered a huge rebase but is ready for a review. |
89c7546
to
c936b9f
Compare
3932ba8
to
7618e8d
Compare
7618e8d
to
ceec72c
Compare
ceec72c
to
1d23913
Compare
b525de5
to
a4ceeb0
Compare
Hi @noisysocks,
Previously we were showing "Block Areas" widget in legacy widgets. This PR fixes that. The empty dropdown was a bug and not it shows as expected a message saying there is no widget available.
There are some known problems where some third-party widgets don't work because of relying on a specific dom. That said the Image widget seems to work on my tests: Is there any error being logged during your tests? |
We show the currently selected widget in the inspector: We are trying to follow what's happening in other blocks. If we have multiple paragraph blocks, it is not easy to tell which type of paragraph a block is. The user would only know by selecting the block and checking its name on the inspector. |
I'm torn on this one. I'd like to stay inline with how we treat other blocks. But in this case, it's not a block, it's a widget inside a block. Can we adopt the reusable block pattern of displaying a name? Maybe we don't include the "Edit" button in this case though. |
6af688d
to
2cbc838
Compare
Hi @noisysocks, @mapk, It follows the logic for the title in the widgets screen can show just the type of widget as a title or type plus the specific instance title set by the user. I think this PR should be ready. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fundamentally, I think things here are looking really good!
The code that renders a widget is pretty tough to follow (as it is in Core 😅), but I think we can aim to clean a lot of that up if/when this feature is merged into Core e.g. by splitting dynamic_sidebar()
up.
Before we merge this, I'd really like to see us rethink the REST API design and possibly just ditching it altogether in favour of a regular AJAX action. Happy to chat about this—ping me!
Have you thought about adding E2E tests for widgets? (Can be done as a follow-up.)
@@ -34,13 +34,30 @@ public function __construct() { | |||
public function register_routes() { | |||
register_rest_route( | |||
$this->namespace, | |||
// Regex representing a PHP class extracted from http://php.net/manual/en/language.oop5.basic.php. | |||
'/' . $this->rest_base . '/(?P<identifier>[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)/', | |||
'/' . $this->rest_base . '/', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we need to rethink this route and how to make it RESTful / align with the rest of the WordPress REST API.
There's three odd things:
-
The URL (
/wp/v2/widgets
) indicates that the route refers to a collection resource, but it's actually referring to a member resource. We should put the identifier of the resource in the URL (e.g./wp/v2/widgets/:identifier
). We could do this by either splitting the route into/wp/v2/class-widgets/:className
and/wp/instance-widgets/:identifier
, or by prefixing the identifier with the type of identifier, e.g./wp/v2/widgets/<class|instance>:identifier
. -
It looks like, so long as plugins are not doing anything weird, that this route won't modify any state on the server. Is that true? If so, this route is safe and should be using
GET
instead ofPOST
. -
The name of the route is
/wp/v2/widgets
, but really this route returns a widget form given a specific widget. I'd suggest renaming to/wp/v2/widget-forms
.
Alternatively, could we ditch REST and make this an AJAX RPC-style endpoint? It might be better a fit since this endpoint is so very highly specific to how WP Admin is implemented (in other words: third parties will likely not find this route useful), and it isn't very clear what the resource the endpoint refers to is.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hi @noisysocks, I ended up applying another big change to this PR.
I noticed that if we call the existing ajax admin endpoint without any changes, I retrieve its edit form. So instead of reimplementing this logic for reference widgets, I'm just relying on the endpoint.
For the class widgets, we can not use the endpoint, because there is not a widget instance at all. I kept the rest endpoint but modified it as suggested to be clear of what resource we provide a widget-form.
It may be useful e.g: to build an alternative widget edit UI, we can use this endpoint to retrieve a widget edit form and we can use the server-side render endpoint to retrieve the widget frontend.
I could have made it an ajax endpoint but the code would be more complex, e.g: we get validation and permission checked by the rest API.
Regarding the POST vs GET, the problem with get is that URLs have a max character limit depending on the browser. A widget may have a text area and use lots of character so it seemed safer to use POST. Also, widgets may have legitimate reasons to have side effects e.g: file uploads. Using a get this widgets would break.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I could have made it an ajax endpoint but the code would be more complex, e.g: we get validation and permission checked by the rest API.
I still have a slight preference for using an AJAX endpoint instead of a REST API endpoint because of how implementation-specific this endpoint is compared to the rest (ha!) of the REST API. Happy to bite my tongue and have us iterate on this, though. I'm glad that the endpoint has been marked experimental 🙂
It might be good to loop in the REST API team at some point on this question.
Regarding the POST vs GET, the problem with get is that URLs have a max character limit depending on the browser. A widget may have a text area and use lots of character so it seemed safer to use POST. Also, widgets may have legitimate reasons to have side effects e.g: file uploads. Using a get this widgets would break.
Makes sense, thanks 👍
lib/load.php
Outdated
@@ -20,6 +20,8 @@ | |||
} | |||
if ( ! class_exists( 'WP_REST_Widget_Areas_Controller' ) ) { | |||
require dirname( __FILE__ ) . '/class-experimental-wp-widget-blocks-manager.php'; | |||
} | |||
if ( ! class_exists( 'WP_REST_Widget_Areas_Controller' ) ) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These two if
s are the same.
wp_nonce_field( 'save-sidebar-widgets', '_wpnonce_widgets', false ), | ||
'</form>', | ||
) | ||
); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What's this for?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added a comment. Basically for the widgets code to be able to call the ajax endpoint we need a nonce.
isset( $widget_obj['callback'][0] ) && | ||
( $widget_obj['callback'][0] instanceof WP_Widget ) | ||
( $widget_obj['callback'][0] instanceof WP_Widget ) ) || | ||
strncmp( $widget_id, $block_widget_start, strlen( $block_widget_start ) ) === 0 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's more common (and, I think, easier to understand) to see substr
used to check that a string begins with another string.
substr( $widget_id, 0, strlen( $block_widget_start ) ) === $block_widget_start
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Performance is not very relevant in this case. But, substring first unconditionally iterates on the string to copy parts of it and the only in the end the comparison is done. The method I'm using does not copy any character and stops the comparison when the first char difference is found. So I normally use this code because it is more performant. I added a comment to say that this logic is a starts with.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Interesting! PHP needs a starts_with
function 😛
* @return string Returns the rendered widget as a string. | ||
*/ | ||
function render_widget_by_id( $id ) { | ||
// Code extracted from src/wp-includes/widgets.php. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's mention which method this code comes from (dynamic_sidebar()
, yeah?).
When this code is merged into Core, I'd like to see this part of dynamic_sidebar()
split out into its own method. We'll need to remember to do this somehow e.g. a Trac ticket or a TODO
comment here.
d4d26dd
to
4b15b74
Compare
Hi @noisysocks, I applied a considerable reactor in this PR and addressed most of your suggestions. Please, let me know what are your thoughts on the PR now. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a big improvement—let's get it in so that it gets the testing and iteration that it deserves. Thanks for the hard work, Jorge! 🙂
<input type="hidden" name="widget_number" className="widget_number" value={ widgetNumber } /> | ||
<input type="hidden" name="multi_number" className="multi_number" value="" /> | ||
<input type="hidden" name="add_new" className="add_new" value="" /> | ||
{ isReferenceWidget && ( <><input type="hidden" name="widget-id" className="widget-id" value={ id } /> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: Let's put a newline in between the <>
and <input>
.
'idBase' => array( | ||
'type' => 'string', | ||
), | ||
'widgetNumber' => array( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: Is it redundant to prefix all of these attributes with widget
? The block is called a legacy-widget
, after all.
formData.append( 'widget-height', '200' ); | ||
formData.append( 'savewidgets', this.widgetNonce ); | ||
httpRequest.open( 'POST', window.ajaxurl ); | ||
const self = this; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's use a =>
function instead of a function()
function below so that const self = this;
isn't necessary.
4b15b74
to
b3b2254
Compare
b3b2254
to
e1e48ac
Compare
Description
Fixes: #16604
This is a WIP it's working correctly but I still need to check the code to improve it.
During the widgets RFC, we decided that existing widgets will be referenced in the legacy widgets and the real instance should be updated in these cases.
This PR uses the ajax admin endpoint to implement this concept of referencing widgets in the legacy widgets block.
This mechanism is also used to implement callback widgets so this PR supersedes #14395 and allows us to avoid the hack
$_POST = array_merge( $_POST, $instance_changes );
present in the other PR.The rest endpoint is used to get the initial form of all widgets (the ajax admin does not seem to allow that), and it is used to provide the update logic in class widgets that don't have an instance. That endpoint should be read-only unless the widgets apply changes in methods that should not have side-effects.
The ajax admin is used to perform updates on legacy widgets that reference a real widget instance present in the database.
How has this been tested?
I pasted the following code in the block editor:
(Update identifier to real widget instances that were create using the widgets screen).
I verified the legacy widget block works as expected and if I open the widgets screen the changes I did appear there.
I used "NattyWP feedburner widget". The widget comes with silesia theme that can be downloaded at wordpress.org/themes/silesia. The theme is not updated to WordPress 5.0 and some warning messages appear. For testing purposes, the widget can also be used standalone by pasting the code available themes.trac.wordpress.org/browser/silesia/1.0.6/include/widgets/feedburner.php in the functions.php of any other theme.