-
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
Try Legacy widget block #13511
Try Legacy widget block #13511
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,186 @@ | ||
<?php | ||
/** | ||
* Start: Include for phase 2 | ||
* Widget Updater REST API: WP_REST_Widget_Updater_Controller class | ||
* | ||
* @package gutenberg | ||
* @since 5.2.0 | ||
*/ | ||
|
||
/** | ||
* Controller which provides REST endpoint for updating a widget. | ||
* | ||
* @since 5.2.0 | ||
* | ||
* @see WP_REST_Controller | ||
*/ | ||
class WP_REST_Widget_Updater_Controller extends WP_REST_Controller { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We're missing unit tests for this controller. WordPress has a really great API for writing REST API tests, check out trunk for examples: https://core.trac.wordpress.org/browser/trunk/tests/phpunit/tests/rest-api?order=name |
||
|
||
/** | ||
* Constructs the controller. | ||
* | ||
* @access public | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think that we usually use |
||
*/ | ||
public function __construct() { | ||
$this->namespace = 'wp/v2'; | ||
$this->rest_base = 'widgets'; | ||
} | ||
|
||
/** | ||
* Registers the necessary REST API route. | ||
* | ||
* @access public | ||
*/ | ||
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]*)/', | ||
array( | ||
'args' => array( | ||
'identifier' => array( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hi @noisysocks, |
||
'description' => __( 'Class name of the widget.', 'gutenberg' ), | ||
'type' => 'string', | ||
), | ||
), | ||
array( | ||
'methods' => WP_REST_Server::EDITABLE, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Does this method make any changes on the server that are visible to future requests? If not, it's considered safe and is more of a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Initially, I used EDDITABLE because we may have a widget that has side effects (that's unexpected but we never know what plugins will do). With the improvements being made in #14395, for callback widgets now we apply a change so it makes sense to use editable. |
||
'permission_callback' => array( $this, 'compute_new_widget_permissions_check' ), | ||
'callback' => array( $this, 'compute_new_widget' ), | ||
), | ||
jorgefilipecosta marked this conversation as resolved.
Show resolved
Hide resolved
|
||
) | ||
); | ||
} | ||
|
||
/** | ||
* Checks if the user has permissions to make the request. | ||
* | ||
* @since 5.2.0 | ||
* @access public | ||
* | ||
* @return true|WP_Error True if the request has read access, WP_Error object otherwise. | ||
*/ | ||
public function compute_new_widget_permissions_check() { | ||
// Verify if the current user has edit_theme_options capability. | ||
// This capability is required to access the widgets screen. | ||
if ( ! current_user_can( 'edit_theme_options' ) ) { | ||
return new WP_Error( | ||
'widgets_cannot_access', | ||
__( 'Sorry, you are not allowed to access widgets on this site.', 'gutenberg' ), | ||
array( | ||
'status' => rest_authorization_required_code(), | ||
) | ||
); | ||
} | ||
return true; | ||
} | ||
|
||
/** | ||
* Returns the new widget instance and the form that represents it. | ||
* | ||
* @since 5.2.0 | ||
* @access public | ||
* | ||
* @param WP_REST_Request $request Full details about the request. | ||
* @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. | ||
*/ | ||
public function compute_new_widget( $request ) { | ||
$url_params = $request->get_url_params(); | ||
|
||
$widget = $request->get_param( 'identifier' ); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We can't use the class name (e.g. function my_widget_callback() {
echo '<marquee>This is a legacy widget!</marquee>';
}
wp_register_sidebar_widget( 'my-widget', 'My cool widget', 'my_widget_callback' ); Widgets registered this way I haven't looked enough at your PR to make a recommendation on what to do, but I am thinking you might need to use the widget ID (e.g. I'm currently dealing with the same roadblock while working on how to store blocks within our widgets architecture, so let me know if you want to chat about widgets—it's confusing! 🙂 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. OK, so I am thinking that we may have to make the block contain only the widget's identifier as an attribute, and nothing else:
Rendering the widget's frontend would work by calling Rendering the widget's backend would work by calling Updating the widget would work similar to how we currently support meta boxes: on save, we serialise the widget's Does that make sense, or have I missed something? I'm still learning how widgets are implemented in WordPress so take everything I say with a grain of salt! 🧂 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Hi @noisysocks, good catch! I missed this type of widgets. I updated the code to correct the problem. The sample code you provided now works as expected: There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Hi @noisysocks This path has some problems though:
Saving the widget instance as standard block attributes seemed to overcome this limitations. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That makes sense Jorge. Thanks for thinking it through! My only concern now is that it's not clear how we will support callback widgets that use both |
||
|
||
global $wp_widget_factory; | ||
|
||
if ( | ||
null === $widget || | ||
! isset( $wp_widget_factory->widgets[ $widget ] ) || | ||
! ( $wp_widget_factory->widgets[ $widget ] instanceof WP_Widget ) | ||
jorgefilipecosta marked this conversation as resolved.
Show resolved
Hide resolved
|
||
) { | ||
return new WP_Error( | ||
'widget_invalid', | ||
__( 'Invalid widget.', 'gutenberg' ), | ||
array( | ||
'status' => 404, | ||
) | ||
); | ||
} | ||
|
||
$widget_obj = $wp_widget_factory->widgets[ $widget ]; | ||
|
||
$instance = $request->get_param( 'instance' ); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm confused: why do we need both There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The instance represents, the previous widget instance normally saved on the database (in our case save as the block attributes). Instance changes represent the fields that are being changed. That's how the widgets work, the update method receives the previous instance and the changes and returns a new instance. |
||
if ( null === $instance ) { | ||
$instance = array(); | ||
} | ||
$id_to_use = $request->get_param( 'id_to_use' ); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why does the client need to provide this? What would be wrong with the server always using There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The server uses this value to compute the id's used in the forms. Most JavaScript widgets have a check and if an id was initialized before they do nothing. So we need a unique id per widget instance if we used -1 all the time most JS widgets would only support a single instance. |
||
if ( null === $id_to_use ) { | ||
$id_to_use = -1; | ||
} | ||
|
||
$widget_obj->_set( $id_to_use ); | ||
ob_start(); | ||
|
||
$instance_changes = $request->get_param( 'instance_changes' ); | ||
if ( null !== $instance_changes ) { | ||
$old_instance = $instance; | ||
$instance = $widget_obj->update( $instance_changes, $old_instance ); | ||
jorgefilipecosta marked this conversation as resolved.
Show resolved
Hide resolved
|
||
/** | ||
* Filters a widget's settings before saving. | ||
* | ||
* Returning false will effectively short-circuit the widget's ability | ||
* to update settings. The old setting will be returned. | ||
* | ||
* @since 5.2.0 | ||
* | ||
* @param array $instance The current widget instance's settings. | ||
* @param array $instance_changes Array of new widget settings. | ||
* @param array $old_instance Array of old widget settings. | ||
* @param WP_Widget $widget_ob The widget instance. | ||
*/ | ||
$instance = apply_filters( 'widget_update_callback', $instance, $instance_changes, $old_instance, $widget_obj ); | ||
if ( false === $instance ) { | ||
$instance = $old_instance; | ||
} | ||
} | ||
|
||
$instance = apply_filters( 'widget_form_callback', $instance, $widget_obj ); | ||
|
||
$return = null; | ||
if ( false !== $instance ) { | ||
$return = $widget_obj->form( $instance ); | ||
|
||
/** | ||
* Fires at the end of the widget control form. | ||
* | ||
* Use this hook to add extra fields to the widget form. The hook | ||
* is only fired if the value passed to the 'widget_form_callback' | ||
* hook is not false. | ||
* | ||
* Note: If the widget has no form, the text echoed from the default | ||
* form method can be hidden using CSS. | ||
* | ||
* @since 5.2.0 | ||
* | ||
* @param WP_Widget $widget_obj The widget instance (passed by reference). | ||
* @param null $return Return null if new fields are added. | ||
* @param array $instance An array of the widget's settings. | ||
*/ | ||
do_action_ref_array( 'in_widget_form', array( &$widget_obj, &$return, $instance ) ); | ||
} | ||
|
||
$id_base = $widget_obj->id_base; | ||
$id = $widget_obj->id; | ||
$form = ob_get_clean(); | ||
|
||
return rest_ensure_response( | ||
array( | ||
'instance' => $instance, | ||
'form' => $form, | ||
'id_base' => $id_base, | ||
'id' => $id, | ||
) | ||
); | ||
} | ||
} | ||
/** | ||
* End: Include for phase 2 | ||
*/ |
Original file line number | Diff line number | Diff line change | ||
---|---|---|---|---|
|
@@ -872,34 +872,83 @@ function gutenberg_editor_scripts_and_styles( $hook ) { | |||
); | ||||
} | ||||
|
||||
/** | ||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As by the previous note, this function doesn't exist as of #13569. All new functionality should extend the block editor as if it were any other plugin extending the core block editor. |
||||
* Start: Include for phase 2 | ||||
*/ | ||||
|
||||
/** | ||||
* Todo: The hardcoded array should be replaced with a mechanisms that allows core blocks | ||||
* and third party blocks to specify they already have equivalent blocks, and maybe even allow them | ||||
* to have a migration function. | ||||
*/ | ||||
$core_widgets = array( 'WP_Widget_Pages', 'WP_Widget_Calendar', 'WP_Widget_Archives', 'WP_Widget_Media_Audio', 'WP_Widget_Media_Image', 'WP_Widget_Media_Gallery', 'WP_Widget_Media_Video', 'WP_Widget_Meta', 'WP_Widget_Search', 'WP_Widget_Text', 'WP_Widget_Categories', 'WP_Widget_Recent_Posts', 'WP_Widget_Recent_Comments', 'WP_Widget_RSS', 'WP_Widget_Tag_Cloud', 'WP_Nav_Menu_Widget', 'WP_Widget_Custom_HTML' ); | ||||
|
||||
$has_permissions_to_manage_widgets = current_user_can( 'edit_theme_options' ); | ||||
$available_legacy_widgets = array(); | ||||
global $wp_widget_factory, $wp_registered_widgets; | ||||
foreach ( $wp_widget_factory->widgets as $class => $widget_obj ) { | ||||
if ( ! in_array( $class, $core_widgets ) ) { | ||||
$available_legacy_widgets[ $class ] = array( | ||||
'name' => html_entity_decode( $widget_obj->name ), | ||||
'description' => html_entity_decode( $widget_obj->widget_options['description'] ), | ||||
'isCallbackWidget' => false, | ||||
); | ||||
} | ||||
} | ||||
foreach ( $wp_registered_widgets as $widget_id => $widget_obj ) { | ||||
if ( | ||||
is_array( $widget_obj['callback'] ) && | ||||
isset( $widget_obj['callback'][0] ) && | ||||
( $widget_obj['callback'][0] instanceof WP_Widget ) | ||||
) { | ||||
continue; | ||||
} | ||||
$available_legacy_widgets[ $widget_id ] = array( | ||||
'name' => html_entity_decode( $widget_obj['name'] ), | ||||
'description' => null, | ||||
jorgefilipecosta marked this conversation as resolved.
Show resolved
Hide resolved
|
||||
'isCallbackWidget' => true, | ||||
); | ||||
} | ||||
/** | ||||
* End: Include for phase 2 | ||||
*/ | ||||
|
||||
$editor_settings = array( | ||||
'alignWide' => $align_wide, | ||||
'availableTemplates' => $available_templates, | ||||
'allowedBlockTypes' => $allowed_block_types, | ||||
'disableCustomColors' => get_theme_support( 'disable-custom-colors' ), | ||||
'disableCustomFontSizes' => get_theme_support( 'disable-custom-font-sizes' ), | ||||
'disablePostFormats' => ! current_theme_supports( 'post-formats' ), | ||||
'titlePlaceholder' => apply_filters( 'enter_title_here', __( 'Add title', 'gutenberg' ), $post ), | ||||
'bodyPlaceholder' => apply_filters( 'write_your_story', __( 'Start writing or type / to choose a block', 'gutenberg' ), $post ), | ||||
'isRTL' => is_rtl(), | ||||
'autosaveInterval' => 10, | ||||
'maxUploadFileSize' => $max_upload_size, | ||||
'allowedMimeTypes' => get_allowed_mime_types(), | ||||
'styles' => $styles, | ||||
'imageSizes' => gutenberg_get_available_image_sizes(), | ||||
'richEditingEnabled' => user_can_richedit(), | ||||
'alignWide' => $align_wide, | ||||
'availableTemplates' => $available_templates, | ||||
/** | ||||
* Start: Include for phase 2 | ||||
*/ | ||||
'hasPermissionsToManageWidgets' => $has_permissions_to_manage_widgets, | ||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It doesn't seem like this should be an editor setting. We have precedent for this sort of permissions check using REST API links. gutenberg/packages/edit-post/src/components/header/post-publish-button-or-toggle.js Line 82 in 1386ab4
|
||||
'availableLegacyWidgets' => $available_legacy_widgets, | ||||
/** | ||||
* End: Include for phase 2 | ||||
*/ | ||||
'allowedBlockTypes' => $allowed_block_types, | ||||
'disableCustomColors' => get_theme_support( 'disable-custom-colors' ), | ||||
'disableCustomFontSizes' => get_theme_support( 'disable-custom-font-sizes' ), | ||||
'disablePostFormats' => ! current_theme_supports( 'post-formats' ), | ||||
'titlePlaceholder' => apply_filters( 'enter_title_here', __( 'Add title', 'gutenberg' ), $post ), | ||||
'bodyPlaceholder' => apply_filters( 'write_your_story', __( 'Start writing or type / to choose a block', 'gutenberg' ), $post ), | ||||
'isRTL' => is_rtl(), | ||||
'autosaveInterval' => 10, | ||||
'maxUploadFileSize' => $max_upload_size, | ||||
'allowedMimeTypes' => get_allowed_mime_types(), | ||||
'styles' => $styles, | ||||
'imageSizes' => gutenberg_get_available_image_sizes(), | ||||
'richEditingEnabled' => user_can_richedit(), | ||||
|
||||
// Ideally, we'd remove this and rely on a REST API endpoint. | ||||
'postLock' => $lock_details, | ||||
'postLockUtils' => array( | ||||
'postLock' => $lock_details, | ||||
'postLockUtils' => array( | ||||
'nonce' => wp_create_nonce( 'lock-post_' . $post->ID ), | ||||
'unlockNonce' => wp_create_nonce( 'update-post_' . $post->ID ), | ||||
'ajaxUrl' => admin_url( 'admin-ajax.php' ), | ||||
), | ||||
|
||||
// Whether or not to load the 'postcustom' meta box is stored as a user meta | ||||
// field so that we're not always loading its assets. | ||||
'enableCustomFields' => (bool) get_user_meta( get_current_user_id(), 'enable_custom_fields', true ), | ||||
'enableCustomFields' => (bool) get_user_meta( get_current_user_id(), 'enable_custom_fields', true ), | ||||
); | ||||
|
||||
$post_autosave = gutenberg_get_autosave_newer_than_post_save( $post ); | ||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -51,3 +51,22 @@ function gutenberg_filter_oembed_result( $response, $handler, $request ) { | |
); | ||
} | ||
add_filter( 'rest_request_after_callbacks', 'gutenberg_filter_oembed_result', 10, 3 ); | ||
|
||
|
||
|
||
/** | ||
* Start: Include for phase 2 | ||
*/ | ||
/** | ||
* Registers the REST API routes needed by the legacy widget block. | ||
* | ||
* @since 5.0.0 | ||
*/ | ||
function gutenberg_register_rest_widget_updater_routes() { | ||
$widgets_controller = new WP_REST_Widget_Updater_Controller(); | ||
$widgets_controller->register_routes(); | ||
} | ||
add_action( 'rest_api_init', 'gutenberg_register_rest_widget_updater_routes' ); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We already have |
||
/** | ||
* End: Include for phase 2 | ||
*/ |
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.
We must find another way to extend this. It's slated for removal in #13569.
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.
Does it need to occur where it is in the markup here? Or could it be done as hook handlers to
admin_print_footer_scripts
andadmin_footer
?