mirrored from git://develop.git.wordpress.org/
-
Notifications
You must be signed in to change notification settings - Fork 2.5k
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
REST API: Introduce Plugins and Block Directory endpoint #359
Closed
Closed
Changes from 1 commit
Commits
Show all changes
9 commits
Select commit
Hold shift + click to select a range
eccd6a2
First pass at plugins & block directory backport
TimothyBJacobs 1aad910
Fix install plugin tests. Linting.
TimothyBJacobs 385b48c
Delete admin user in teardown.
TimothyBJacobs 88f6e77
Use CDN for block directory assets
TimothyBJacobs e2fe874
Check that there is at least one other plugin active, not more than one.
TimothyBJacobs ef134a4
Enequeue block directory scripts and styles
tellyworth bb3abed
Fix last_updated date for blocks. Add field to schema. Use mock data …
TimothyBJacobs 82a764a
Switch to gmdate
TimothyBJacobs 7946dc6
Don't exclude installed plugins from the block directory search.
TimothyBJacobs File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
335 changes: 335 additions & 0 deletions
335
src/wp-includes/rest-api/endpoints/class-wp-rest-block-directory-controller.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,335 @@ | ||
<?php | ||
/** | ||
* REST API: WP_REST_Block_Directory_Controller class | ||
* | ||
* @package WordPress | ||
* @subpackage REST_API | ||
* @since 5.5.0 | ||
*/ | ||
|
||
/** | ||
* Controller which provides REST endpoint for the blocks. | ||
* | ||
* @since 5.5.0 | ||
* | ||
* @see WP_REST_Controller | ||
*/ | ||
class WP_REST_Block_Directory_Controller extends WP_REST_Controller { | ||
|
||
/** | ||
* Constructs the controller. | ||
*/ | ||
public function __construct() { | ||
$this->namespace = 'wp/v2'; | ||
$this->rest_base = 'block-directory'; | ||
} | ||
|
||
/** | ||
* Registers the necessary REST API routes. | ||
*/ | ||
public function register_routes() { | ||
register_rest_route( | ||
$this->namespace, | ||
'/' . $this->rest_base . '/search', | ||
array( | ||
array( | ||
'methods' => WP_REST_Server::READABLE, | ||
'callback' => array( $this, 'get_items' ), | ||
'permission_callback' => array( $this, 'get_items_permissions_check' ), | ||
'args' => $this->get_collection_params(), | ||
), | ||
'schema' => array( $this, 'get_public_item_schema' ), | ||
) | ||
); | ||
} | ||
|
||
/** | ||
* Checks whether a given request has permission to install and activate plugins. | ||
* | ||
* @since 5.5.0 | ||
* | ||
* @param WP_REST_Request $request Full details about the request. | ||
* | ||
* @return WP_Error|bool True if the request has permission, WP_Error object otherwise. | ||
*/ | ||
public function get_items_permissions_check( $request ) { | ||
if ( ! current_user_can( 'install_plugins' ) || ! current_user_can( 'activate_plugins' ) ) { | ||
return new WP_Error( | ||
'rest_block_directory_cannot_view', | ||
__( 'Sorry, you are not allowed to browse the block directory.' ), | ||
array( 'status' => rest_authorization_required_code() ) | ||
); | ||
} | ||
|
||
return true; | ||
} | ||
|
||
/** | ||
* Search and retrieve blocks metadata | ||
* | ||
* @since 5.5.0 | ||
* | ||
* @param WP_REST_Request $request Full details about the request. | ||
* | ||
* @return WP_Error|WP_REST_Response Response object on success, or WP_Error object on failure. | ||
*/ | ||
public function get_items( $request ) { | ||
require_once ABSPATH . 'wp-admin/includes/plugin-install.php'; | ||
require_once ABSPATH . 'wp-admin/includes/plugin.php'; | ||
|
||
$response = plugins_api( | ||
'query_plugins', | ||
array( | ||
'block' => $request['term'], | ||
'per_page' => $request['per_page'], | ||
'page' => $request['page'], | ||
) | ||
); | ||
|
||
if ( is_wp_error( $response ) ) { | ||
$response->add_data( array( 'status' => 500 ) ); | ||
|
||
return $response; | ||
} | ||
|
||
$result = array(); | ||
|
||
foreach ( $response->plugins as $plugin ) { | ||
if ( $this->find_plugin_for_slug( $plugin['slug'] ) ) { | ||
continue; | ||
} | ||
|
||
$data = $this->prepare_item_for_response( $plugin, $request ); | ||
$result[] = $this->prepare_response_for_collection( $data ); | ||
} | ||
|
||
return rest_ensure_response( $result ); | ||
} | ||
|
||
/** | ||
* Parse block metadata for a block, and prepare it for an API repsonse. | ||
* | ||
* @since 5.5.0 | ||
* | ||
* @param array $plugin The plugin metadata. | ||
* @param WP_REST_Request $request Request object. | ||
* | ||
* @return WP_Error|WP_REST_Response Response object on success, or WP_Error object on failure. | ||
*/ | ||
public function prepare_item_for_response( $plugin, $request ) { | ||
// There might be multiple blocks in a plugin. Only the first block is mapped. | ||
$block_data = reset( $plugin['blocks'] ); | ||
|
||
// A data array containing the properties we'll return. | ||
$block = array( | ||
'name' => $block_data['name'], | ||
'title' => ( $block_data['title'] ? $block_data['title'] : $plugin['name'] ), | ||
'description' => wp_trim_words( $plugin['description'], 30, '...' ), | ||
'id' => $plugin['slug'], | ||
'rating' => $plugin['rating'] / 20, | ||
'rating_count' => intval( $plugin['num_ratings'] ), | ||
'active_installs' => intval( $plugin['active_installs'] ), | ||
'author_block_rating' => $plugin['author_block_rating'] / 20, | ||
'author_block_count' => intval( $plugin['author_block_count'] ), | ||
'author' => wp_strip_all_tags( $plugin['author'] ), | ||
'icon' => ( isset( $plugin['icons']['1x'] ) ? $plugin['icons']['1x'] : 'block-default' ), | ||
'assets' => array(), | ||
'last_updated' => $plugin['last_updated'], | ||
'humanized_updated' => sprintf( | ||
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 would like to remove this, but this is blocked by WordPress/gutenberg#14486. 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. Yeah agreed. We can drop it in future I guess. |
||
/* translators: %s: Human-readable time difference. */ | ||
__( '%s ago' ), | ||
human_time_diff( strtotime( $plugin['last_updated'] ) ) | ||
), | ||
); | ||
|
||
foreach ( $plugin['block_assets'] as $asset ) { | ||
// TODO: Return from API, not client-set. | ||
TimothyBJacobs marked this conversation as resolved.
Show resolved
Hide resolved
|
||
$block['assets'][] = 'https://plugins.svn.wordpress.org/' . $plugin['slug'] . $asset; | ||
} | ||
|
||
$this->add_additional_fields_to_object( $block, $request ); | ||
|
||
$response = new WP_REST_Response( $block ); | ||
$response->add_links( $this->prepare_links( $plugin ) ); | ||
|
||
return $response; | ||
} | ||
|
||
/** | ||
* Generates a list of links to include in the response for the plugin. | ||
* | ||
* @since 5.5.0 | ||
* | ||
* @param array $plugin The plugin data from WordPress.org. | ||
* | ||
* @return array | ||
*/ | ||
protected function prepare_links( $plugin ) { | ||
$links = array( | ||
'https://api.w.org/install-plugin' => array( | ||
'href' => add_query_arg( 'slug', urlencode( $plugin['slug'] ), rest_url( 'wp/v2/plugins' ) ), | ||
), | ||
); | ||
|
||
$plugin_file = $this->find_plugin_for_slug( $plugin['slug'] ); | ||
|
||
if ( $plugin_file ) { | ||
$links['https://api.w.org/plugin'] = array( | ||
'href' => rest_url( 'wp/v2/plugins/' . substr( $plugin_file, 0, - 4 ) ), | ||
'embeddable' => true, | ||
); | ||
} | ||
|
||
return $links; | ||
} | ||
|
||
/** | ||
* Finds an installed plugin for the given slug. | ||
* | ||
* @since 5.5.0 | ||
* | ||
* @param string $slug The WordPress.org directory slug for a plugin. | ||
* | ||
* @return string The plugin file found matching it. | ||
*/ | ||
protected function find_plugin_for_slug( $slug ) { | ||
require_once ABSPATH . 'wp-admin/includes/plugin.php'; | ||
|
||
$plugin_files = get_plugins( '/' . $slug ); | ||
|
||
if ( ! $plugin_files ) { | ||
return ''; | ||
} | ||
|
||
$plugin_files = array_keys( $plugin_files ); | ||
|
||
return $slug . '/' . reset( $plugin_files ); | ||
} | ||
|
||
/** | ||
* Retrieves the theme's schema, conforming to JSON Schema. | ||
* | ||
* @since 5.5.0 | ||
* | ||
* @return array Item schema data. | ||
*/ | ||
public function get_item_schema() { | ||
if ( $this->schema ) { | ||
return $this->add_additional_fields_schema( $this->schema ); | ||
} | ||
|
||
$this->schema = array( | ||
'$schema' => 'http://json-schema.org/draft-04/schema#', | ||
'title' => 'block-directory-item', | ||
'type' => 'object', | ||
'properties' => array( | ||
'name' => array( | ||
'description' => __( 'The block name, in namespace/block-name format.' ), | ||
'type' => 'string', | ||
'context' => array( 'view' ), | ||
), | ||
'title' => array( | ||
'description' => __( 'The block title, in human readable format.' ), | ||
'type' => 'string', | ||
'context' => array( 'view' ), | ||
), | ||
'description' => array( | ||
'description' => __( 'A short description of the block, in human readable format.' ), | ||
'type' => 'string', | ||
'context' => array( 'view' ), | ||
), | ||
'id' => array( | ||
'description' => __( 'The block slug.' ), | ||
'type' => 'string', | ||
'context' => array( 'view' ), | ||
), | ||
'rating' => array( | ||
'description' => __( 'The star rating of the block.' ), | ||
'type' => 'integer', | ||
'context' => array( 'view' ), | ||
), | ||
'rating_count' => array( | ||
'description' => __( 'The number of ratings.' ), | ||
'type' => 'integer', | ||
'context' => array( 'view' ), | ||
), | ||
'active_installs' => array( | ||
'description' => __( 'The number sites that have activated this block.' ), | ||
'type' => 'string', | ||
'context' => array( 'view' ), | ||
), | ||
'author_block_rating' => array( | ||
'description' => __( 'The average rating of blocks published by the same author.' ), | ||
'type' => 'integer', | ||
'context' => array( 'view' ), | ||
), | ||
'author_block_count' => array( | ||
'description' => __( 'The number of blocks published by the same author.' ), | ||
'type' => 'integer', | ||
'context' => array( 'view' ), | ||
), | ||
'author' => array( | ||
'description' => __( 'The WordPress.org username of the block author.' ), | ||
'type' => 'string', | ||
'context' => array( 'view' ), | ||
), | ||
'icon' => array( | ||
'description' => __( 'The block icon.' ), | ||
'type' => 'string', | ||
'format' => 'uri', | ||
'context' => array( 'view' ), | ||
), | ||
'humanized_updated' => array( | ||
'description' => __( 'The date when the block was last updated, in fuzzy human readable format.' ), | ||
'type' => 'string', | ||
'context' => array( 'view' ), | ||
), | ||
'assets' => array( | ||
'description' => __( 'An object representing the block CSS and JavaScript assets.' ), | ||
'type' => 'array', | ||
'context' => array( 'view' ), | ||
'readonly' => true, | ||
'items' => array( | ||
'type' => 'string', | ||
'format' => 'uri', | ||
), | ||
|
||
), | ||
|
||
), | ||
); | ||
|
||
return $this->add_additional_fields_schema( $this->schema ); | ||
} | ||
|
||
/** | ||
* Retrieves the search params for the blocks collection. | ||
* | ||
* @since 5.5.0 | ||
* | ||
* @return array Collection parameters. | ||
*/ | ||
public function get_collection_params() { | ||
$query_params = parent::get_collection_params(); | ||
|
||
$query_params['context']['default'] = 'view'; | ||
|
||
$query_params['term'] = array( | ||
'description' => __( 'Limit result set to blocks matching the search term.' ), | ||
'type' => 'string', | ||
'required' => true, | ||
'minLength' => 1, | ||
); | ||
|
||
unset( $query_params['search'] ); | ||
|
||
/** | ||
* Filter collection parameters for the block directory controller. | ||
* | ||
* @since 5.5.0 | ||
* | ||
* @param array $query_params JSON Schema-formatted collection parameters. | ||
*/ | ||
return apply_filters( 'rest_block_directory_collection_params', $query_params ); | ||
} | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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.
Probably a question for @tellyworth — How will we ensure the right block is imported here? I know Listicles is an example of this, it pulls out the List Item child block instead of the main Listicles block. The wp.org endpoint could return
parent
, and we filter out based on the current block, or maybe it only returns blocks with no parent?