Created
January 27, 2018 12:06
-
-
Save milindmore22/e1cf135b550b0caf210b74c3eeb6f431 to your computer and use it in GitHub Desktop.
Optimizing default WordPress REST API user endpoint for redundant DB calls to check permissions.
This file contains hidden or 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
<?php | |
/** | |
* Rest endpoint filter to change permission callback of item_permission_check. | |
* | |
* @param type $endpoints return endpoints. | |
*/ | |
public function replace_rest_endpoints_permission_callback( $endpoints ) { | |
if ( isset( $endpoints['/wp/v2/users/(?P<id>[\d]+)'] ) ) { | |
foreach ( $endpoints['/wp/v2/users/(?P<id>[\d]+)'] as $route => &$handlers ) { | |
if ( isset( $handlers['permission_callback'] ) ) { | |
foreach ( $handlers['permission_callback'] as $key => &$handler ) { | |
if ( 'get_item_permissions_check' === $handler ) { | |
$handlers['permission_callback'] = 'cached_get_item_permissions_check'; | |
} | |
} | |
} | |
} | |
} | |
return $endpoints; | |
} | |
add_filter( 'rest_endpoints', 'replace_rest_endpoints_permission_callback', 50 ); | |
/** | |
* Permission check with cached count users. | |
* | |
* @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. | |
*/ | |
function cached_get_item_permissions_check( $request ) { | |
$error = new WP_Error( 'rest_user_invalid_id', __( 'Invalid user ID.' ), array( 'status' => 404 ) ); | |
if ( (int) $request['id'] <= 0 ) { | |
return $error; | |
} | |
$user = get_userdata( (int) $request['id'] ); | |
if ( empty( $user ) || ! $user->exists() ) { | |
return $error; | |
} | |
if ( is_multisite() && ! is_user_member_of_blog( $user->ID ) ) { | |
return $error; | |
} | |
if ( is_wp_error( $user ) ) { | |
return $user; | |
} | |
$types = get_post_types( array( 'show_in_rest' => true ), 'names' ); | |
if ( get_current_user_id() === $user->ID ) { | |
return true; | |
} | |
if ( 'edit' === $request['context'] && ! current_user_can( 'list_users' ) ) { | |
return new WP_Error( 'rest_user_cannot_view', __( 'Sorry, you are not allowed to list users.' ), array( 'status' => rest_authorization_required_code() ) ); | |
} elseif ( ! cached_count_user_posts( $user->ID, $types ) && ! current_user_can( 'edit_user', $user->ID ) && ! current_user_can( 'list_users' ) ) { | |
return new WP_Error( 'rest_user_cannot_view', __( 'Sorry, you are not allowed to list users.' ), array( 'status' => rest_authorization_required_code() ) ); | |
} | |
return true; | |
} | |
/** | |
* Cached version of count_user_posts, which is uncached but doesn't always need to hit the db | |
* | |
* Count_user_posts is generally fast, but it can be easy to end up with many redundant queries. | |
* if it's called several times per request. This allows bypassing the db queries in favor of | |
* the cache | |
* | |
* @param int $user_id user id to get count. | |
* @param int|array $types post type array or string. | |
* @return string Count of users. | |
*/ | |
function cached_count_user_posts( $user_id, $types = 'post' ) { | |
if ( ! is_numeric( $user_id ) ) { | |
return 0; | |
} | |
$cache_key = 'cached_' . (int) $user_id; | |
$cache_group = 'user_posts_count'; | |
$count = wp_cache_get( $cache_key, $cache_group ); | |
if ( false === $count ) { | |
// @codingStandardsIgnoreLine. | |
$count = count_user_posts( $user_id, $types ); | |
// 5 Mins, We don't want to handle cache invalidation. | |
wp_cache_set( $cache_key, $count, $cache_group, 5 * MINUTE_IN_SECONDS ); | |
} | |
return $count; | |
} | |
?> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment