-
Notifications
You must be signed in to change notification settings - Fork 30
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add a REST API endpoint for wporg users (#184)
Allows API access to public wporg user data, given a user's `slug`. Fixes #182
- Loading branch information
1 parent
6e176c6
commit 75e9e80
Showing
3 changed files
with
259 additions
and
0 deletions.
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
217 changes: 217 additions & 0 deletions
217
mu-plugins/rest-api/endpoints/class-wporg-rest-users-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,217 @@ | ||
<?php | ||
|
||
namespace WordPressdotorg\MU_Plugins\REST_API; | ||
|
||
/** | ||
* Users_Controller | ||
*/ | ||
class Users_Controller extends \WP_REST_Users_Controller { | ||
/** | ||
* Constructor. | ||
*/ | ||
public function __construct() { | ||
parent::__construct(); | ||
|
||
$this->namespace = 'wporg/v1'; | ||
} | ||
|
||
/** | ||
* Registers the routes for users. | ||
* | ||
* At this time, this endpoint is exclusively read-only. Other routes from the parent class have been omitted. | ||
* | ||
* @see register_rest_route() | ||
*/ | ||
public function register_routes() { | ||
register_rest_route( | ||
$this->namespace, | ||
'/' . $this->rest_base, | ||
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' ), | ||
) | ||
); | ||
|
||
register_rest_route( | ||
$this->namespace, | ||
'/' . $this->rest_base . '/(?P<slug>[\w-]+)', | ||
array( | ||
'args' => array( | ||
'slug' => array( | ||
'description' => __( 'A unique alphanumeric identifier for the user.', 'wporg' ), | ||
'type' => 'string', | ||
), | ||
), | ||
array( | ||
'methods' => \WP_REST_Server::READABLE, | ||
'callback' => array( $this, 'get_item' ), | ||
'permission_callback' => array( $this, 'get_item_permissions_check' ), | ||
'args' => array( | ||
'context' => $this->get_context_param( array( 'default' => 'view' ) ), | ||
), | ||
), | ||
'schema' => array( $this, 'get_public_item_schema' ), | ||
) | ||
); | ||
} | ||
|
||
/** | ||
* Permissions check for getting all users. | ||
* | ||
* @param \WP_REST_Request $request Full details about the request. | ||
* | ||
* @return true|\WP_Error True if the request has read access, otherwise WP_Error object. | ||
*/ | ||
public function get_items_permissions_check( $request ) { | ||
$check = parent::get_items_permissions_check( $request ); | ||
if ( is_wp_error( $check ) ) { | ||
return $check; | ||
} | ||
|
||
if ( empty( $request['slug'] ) ) { | ||
return new \WP_Error( | ||
'rest_invalid request', | ||
__( 'You must use the slug parameter for requests to this endpoint.', 'wporg' ), | ||
array( 'status' => 400 ) | ||
); | ||
} | ||
|
||
return true; | ||
} | ||
|
||
/** | ||
* Get the user, if the slug is valid. | ||
* | ||
* @param string $slug Supplied slug. | ||
* | ||
* @return \WP_User|\WP_Error True if slug is valid, WP_Error otherwise. | ||
*/ | ||
protected function get_user_by_slug( $slug ) { | ||
$error = new \WP_Error( | ||
'rest_user_invalid_slug', | ||
__( 'Invalid user slug.', 'wporg' ), | ||
array( 'status' => 404 ) | ||
); | ||
|
||
if ( mb_strlen( $slug ) > 50 || ! sanitize_title( $slug ) ) { | ||
return $error; | ||
} | ||
|
||
$user = get_user_by( 'slug', $slug ); | ||
if ( empty( $user ) || ! $user->exists() ) { | ||
return $error; | ||
} | ||
|
||
return $user; | ||
} | ||
|
||
/** | ||
* Checks if a given request has access to read a user. | ||
* | ||
* Modified from the parent method to use slug instead of id and remove capability checks that necessitate | ||
* blog membership. | ||
* | ||
* @param \WP_REST_Request $request Full details about the request. | ||
* | ||
* @return true|\WP_Error True if the request has read access for the item, otherwise WP_Error object. | ||
*/ | ||
public function get_item_permissions_check( $request ) { | ||
$user = $this->get_user_by_slug( $request['slug'] ); | ||
if ( is_wp_error( $user ) ) { | ||
return $user; | ||
} | ||
|
||
return true; | ||
} | ||
|
||
/** | ||
* Retrieves a single user. | ||
* | ||
* Modified from the parent method to use slug instead of id. | ||
* | ||
* @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 get_item( $request ) { | ||
$user = $this->get_user_by_slug( $request['slug'] ); | ||
if ( is_wp_error( $user ) ) { | ||
return $user; | ||
} | ||
|
||
$user = $this->prepare_item_for_response( $user, $request ); | ||
$response = rest_ensure_response( $user ); | ||
|
||
return $response; | ||
} | ||
|
||
/** | ||
* Prepares links for the user request. | ||
* | ||
* @param \WP_User $user User object. | ||
* | ||
* @return array Links for the given user. | ||
*/ | ||
protected function prepare_links( $user ) { | ||
$links = parent::prepare_links( $user ); | ||
|
||
// Prepending / is to avoid replacing the 1 in wporg/v1 if the user ID is 1 :D | ||
$links['self']['href'] = str_replace( '/' . $user->ID, '/' . $user->user_nicename, $links['self']['href'] ); | ||
|
||
$links['collection']['href'] = add_query_arg( | ||
'slug', | ||
$user->user_nicename, | ||
$links['collection']['href'] | ||
); | ||
|
||
return $links; | ||
} | ||
|
||
/** | ||
* Retrieves the query params for collections. | ||
* | ||
* @return array Collection parameters. | ||
*/ | ||
public function get_collection_params() { | ||
$allowed_params = array( | ||
'context' => '', | ||
'page' => '', | ||
'per_page' => '', | ||
'order' => '', | ||
'orderby' => '', | ||
'slug' => '', | ||
); | ||
|
||
$query_params = array_intersect_key( parent::get_collection_params(), $allowed_params ); | ||
|
||
if ( isset( $query_params['orderby']['enum'] ) ) { | ||
$allowed_orderby = array( 'id', 'name', 'slug', 'include_slugs' ); | ||
$query_params['orderby']['enum'] = array_intersect( $query_params['orderby']['enum'], $allowed_orderby ); | ||
} | ||
|
||
return $query_params; | ||
} | ||
|
||
/** | ||
* Retrieves the magical context param. | ||
* | ||
* Prevents usage of contexts (such as edit) that potentially reveal users' sensitive account information. | ||
* | ||
* @param array $args Optional. Additional arguments for context parameter. Default empty array. | ||
* | ||
* @return array Context parameter details. | ||
*/ | ||
public function get_context_param( $args = array() ) { | ||
$context = parent::get_context_param( $args ); | ||
$allowed_contexts = array( 'view', 'embed' ); | ||
|
||
$context['enum'] = array_intersect( $context['enum'], $allowed_contexts ); | ||
|
||
return $context; | ||
} | ||
} |
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,41 @@ | ||
<?php | ||
|
||
namespace WordPressdotorg\MU_Plugins\REST_API; | ||
|
||
/** | ||
* Actions and filters. | ||
*/ | ||
add_action( 'rest_api_init', __NAMESPACE__ . '\initialize_rest_endpoints' ); | ||
add_filter( 'rest_user_query', __NAMESPACE__ . '\modify_user_query_parameters', 10, 2 ); | ||
|
||
/** | ||
* Turn on API endpoints. | ||
* | ||
* @return void | ||
*/ | ||
function initialize_rest_endpoints() { | ||
require_once __DIR__ . '/endpoints/class-wporg-rest-users-controller.php'; | ||
|
||
$users_controller = new Users_Controller(); | ||
$users_controller->register_routes(); | ||
} | ||
|
||
/** | ||
* Tweak the user query to allow for getting users who aren't blog members. | ||
* | ||
* @param array $prepared_args | ||
* @param \WP_REST_Request $request | ||
* | ||
* @return array | ||
*/ | ||
function modify_user_query_parameters( $prepared_args, $request ) { | ||
// Only for this specific endpoint. | ||
if ( '/wporg/v1' !== substr( $request->get_route(), 0, 9 ) ) { | ||
return $prepared_args; | ||
} | ||
|
||
$prepared_args['blog_id'] = 0; // Avoid check for blog membership. | ||
unset( $prepared_args['has_published_posts'] ); // Avoid another check for blog membership. | ||
|
||
return $prepared_args; | ||
} |