Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 6 additions & 4 deletions src/wp-includes/author-template.php
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ function get_the_modified_author( $post = null ) {
if ( ! $last_id ) {
return null;
}
$last_user = get_userdata( $last_id );
$last_user = get_authordata( $last_id );

/**
* Filters the display name of the author who last edited the current post.
Expand Down Expand Up @@ -162,6 +162,8 @@ function the_modified_author() {
*
* @since 2.8.0
* @since 6.9.0 Removed `aim`, `jabber`, and `yim` as valid values for the `$field` parameter.
* @since 7.2.0 The user is retrieved with get_authordata(), so its capability data is only
* loaded when the `caps`, `roles`, or `allcaps` fields are requested.
*
* @global WP_User|false|null $authordata The current author's data.
*
Expand All @@ -176,7 +178,7 @@ function get_the_author_meta( $field = '', $user_id = false ) {
global $authordata;
$user_id = $authordata->ID ?? 0;
} else {
$authordata = get_userdata( $user_id );
$authordata = get_authordata( $user_id );
}

if ( in_array( $field, array( 'login', 'pass', 'nicename', 'email', 'url', 'registered', 'activation_key', 'status' ), true ) ) {
Expand Down Expand Up @@ -399,7 +401,7 @@ function get_author_posts_url( $author_id, $author_nicename = '' ) {
$link = $file . '?author=' . $author_id;
} else {
if ( '' === $author_nicename ) {
$user = get_userdata( $author_id );
$user = get_authordata( $author_id );
if ( ! empty( $user->user_nicename ) ) {
$author_nicename = $user->user_nicename;
}
Expand Down Expand Up @@ -534,7 +536,7 @@ function wp_list_authors( $args = '' ) {
continue;
}

$author = get_userdata( $author_id );
$author = get_authordata( $author_id );

if ( $parsed_args['exclude_admin'] && 'admin' === $author->display_name ) {
continue;
Expand Down
4 changes: 2 additions & 2 deletions src/wp-includes/class-wp-query.php
Original file line number Diff line number Diff line change
Expand Up @@ -4051,7 +4051,7 @@ public function get_queried_object() {
}

if ( $this->queried_object_id ) {
$user = get_userdata( $this->queried_object_id );
$user = get_authordata( $this->queried_object_id );
if ( $user ) {
$this->queried_object = $user;
}
Expand Down Expand Up @@ -4897,7 +4897,7 @@ public function generate_postdata( $post ) {

$id = (int) $post->ID;

$authordata = get_userdata( $post->post_author );
$authordata = get_authordata( (int) $post->post_author );

$currentday = false;
$currentmonth = false;
Expand Down
171 changes: 164 additions & 7 deletions src/wp-includes/class-wp-user.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
* @since 2.0.0
* @since 6.8.0 The `user_pass` property is now hashed using bcrypt by default instead of phpass.
* Existing passwords may still be hashed using phpass.
* @since 7.2.0 The `caps`, `roles`, and `allcaps` properties can be lazy-loaded on first access,
* see WP_User::init() and get_authordata(). Implements the JsonSerializable interface.
*
* @property string $nickname
* @property string $description
Expand Down Expand Up @@ -44,7 +46,7 @@
* @phpstan-property int|numeric-string|'' $user_level
*/
#[AllowDynamicProperties]
class WP_User {
class WP_User implements JsonSerializable {
/**
* User data container.
*
Expand Down Expand Up @@ -111,6 +113,29 @@ class WP_User {
*/
private $site_id = 0;

/**
* Whether the capability data has yet to be loaded.
*
* While true, the `caps`, `roles`, and `allcaps` properties are unset so that
* the first access to any of them is routed through the magic methods, which
* load the data on demand.
*
* @since 7.2.0
* @var bool
*/
private $capability_data_pending = false;

/**
* Whether this instance was initialized without loading capability data.
*
* When true, WP_User::for_site() defers loading the `caps`, `roles`, and `allcaps`
* properties until they are first accessed.
*
* @since 7.2.0
* @var bool
*/
private $short_init = false;

/**
* @since 3.3.0
* @var array
Expand Down Expand Up @@ -176,16 +201,21 @@ public function __construct( $id = 0, $name = '', $site_id = 0 ) {
* Sets up object properties, including capabilities.
*
* @since 3.3.0
* @since 7.2.0 Added the `$short_init` parameter.
*
* @param object $data User DB row object.
* @param int $site_id Optional. The site ID to initialize for.
* @param object $data User DB row object.
* @param int $site_id Optional. The site ID to initialize for.
* @param bool $short_init Optional. Whether to defer loading the capability data until
* the `caps`, `roles`, or `allcaps` properties are first accessed.
* Default false.
*/
public function init( $data, $site_id = 0 ) {
public function init( $data, $site_id = 0, $short_init = false ) {
if ( ! isset( $data->ID ) ) {
$data->ID = 0;
}
$this->data = $data;
$this->ID = (int) $data->ID;
$this->data = $data;
$this->ID = (int) $data->ID;
$this->short_init = $short_init;

$this->for_site( $site_id );
}
Expand Down Expand Up @@ -293,6 +323,11 @@ public function __isset( $key ) {
$key = 'ID';
}

if ( $this->is_capability_property( $key ) ) {
$this->load_capability_data();
return isset( $this->$key );
}

if ( isset( $this->data->$key ) ) {
return true;
}
Expand All @@ -308,11 +343,13 @@ public function __isset( $key ) {
* Magic method for accessing custom fields.
*
* @since 3.3.0
* @since 7.2.0 Returns by reference, so that the lazily loaded `caps`, `roles`, and `allcaps`
* properties can be modified in place, e.g. `$user->roles[] = 'editor'`.
*
* @param string $key User meta key to retrieve.
* @return mixed Value of the given user meta key (if set). If `$key` is 'id', the user ID.
*/
public function __get( $key ) {
public function &__get( $key ) {
if ( 'id' === $key ) {
_deprecated_argument(
'WP_User->id',
Expand All @@ -326,6 +363,18 @@ public function __get( $key ) {
return $this->ID;
}

if ( $this->is_capability_property( $key ) ) {
$this->load_capability_data();

if ( isset( $this->$key ) ) {
return $this->$key;
}

// The property was unset after loading. Only variables can be returned by reference.
$value = null;
return $value;
}

if ( isset( $this->data->$key ) ) {
$value = $this->data->$key;
} else {
Expand Down Expand Up @@ -368,6 +417,12 @@ public function __set( $key, $value ) {
return;
}

if ( $this->is_capability_property( $key ) ) {
$this->load_capability_data();
$this->$key = $value;
return;
}

$this->data->$key = $value;
}

Expand All @@ -391,6 +446,12 @@ public function __unset( $key ) {
);
}

if ( $this->is_capability_property( $key ) ) {
$this->load_capability_data();
unset( $this->$key );
return;
}

if ( isset( $this->data->$key ) ) {
unset( $this->data->$key );
}
Expand Down Expand Up @@ -511,6 +572,12 @@ protected function _init_caps( $cap_key = '' ) {
* and boolean values represent whether the user has that capability.
*/
public function get_role_caps() {
// Loading the capability data calls this method again, once the data is in place.
if ( $this->capability_data_pending ) {
$this->load_capability_data();
return $this->allcaps;
}

$switch_site = false;
if ( is_multisite() && get_current_blog_id() !== $this->site_id ) {
$switch_site = true;
Expand Down Expand Up @@ -867,6 +934,8 @@ public function for_blog( $blog_id = 0 ) {
* Sets the site to operate on. Defaults to the current site.
*
* @since 4.9.0
* @since 7.2.0 For instances initialized with `$short_init`, the capability data is loaded
* on first access of the `caps`, `roles`, or `allcaps` properties.
*
* @global wpdb $wpdb WordPress database abstraction object.
*
Expand All @@ -883,6 +952,18 @@ public function for_site( $site_id = 0 ) {

$this->cap_key = $wpdb->get_blog_prefix( $this->site_id ) . 'capabilities';

if ( $this->short_init ) {
/*
* Unset the capability properties so that the first access to any of them
* goes through the magic methods, which load the data on demand.
*/
unset( $this->caps, $this->roles, $this->allcaps );
$this->capability_data_pending = true;
return;
}

$this->capability_data_pending = false;

$this->caps = $this->get_caps_data();

$this->get_role_caps();
Expand Down Expand Up @@ -916,4 +997,80 @@ private function get_caps_data() {

return $caps;
}

/**
* Loads the capability data, if it has not been loaded yet.
*
* Populates the `caps`, `roles`, and `allcaps` properties.
*
* @since 7.2.0
*/
private function load_capability_data() {
if ( ! $this->capability_data_pending ) {
return;
}

// Clear the flag first, as populating the properties below re-enters the magic methods.
$this->capability_data_pending = false;

$this->caps = $this->get_caps_data();

$this->get_role_caps();
}

/**
* Determines whether a property name is one of the lazily loaded capability properties.
*
* @since 7.2.0
*
* @param string $key Property name.
* @return bool Whether the property is `caps`, `roles`, or `allcaps`.
*/
private function is_capability_property( $key ) {
return in_array( $key, array( 'caps', 'roles', 'allcaps' ), true );
}

/**
* Specifies the data which should be serialized to JSON.
*
* Ensures the lazily loaded capability data is included.
*
* @since 7.2.0
*
* @return array<string, mixed> Data to be serialized to JSON.
*/
#[ReturnTypeWillChange]
public function jsonSerialize() {
$this->load_capability_data();

return array(
'data' => $this->data,
'ID' => $this->ID,
'caps' => $this->caps,
'cap_key' => $this->cap_key,
'roles' => $this->roles,
'allcaps' => $this->allcaps,
'filter' => $this->filter,
);
}

/**
* Returns the data to serialize.
*
* Ensures the lazily loaded capability data is included.
*
* @since 7.2.0
*
* @return array<string, mixed> Object properties to serialize, keyed in the same
* format as native object serialization.
*/
public function __serialize() {
$this->load_capability_data();

/*
* The array cast keeps the null byte delimited scope of non-public property names,
* including those of subclasses, which is the format native unserialization expects.
*/
return (array) $this;
}
}
2 changes: 1 addition & 1 deletion src/wp-includes/class-wp.php
Original file line number Diff line number Diff line change
Expand Up @@ -676,7 +676,7 @@ public function register_globals() {
}

if ( $wp_query->is_author() ) {
$GLOBALS['authordata'] = get_userdata( get_queried_object_id() );
$GLOBALS['authordata'] = get_authordata( get_queried_object_id() );
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/wp-includes/link-template.php
Original file line number Diff line number Diff line change
Expand Up @@ -267,7 +267,7 @@ function get_permalink( $post = 0, $leavename = false ) {

$author = '';
if ( str_contains( $permalink, '%author%' ) ) {
$authordata = get_userdata( $post->post_author );
$authordata = get_authordata( (int) $post->post_author );
$author = $authordata->user_nicename;
}

Expand Down
30 changes: 30 additions & 0 deletions src/wp-includes/user.php
Original file line number Diff line number Diff line change
Expand Up @@ -2028,6 +2028,36 @@ function sanitize_user_field( $field, $value, $user_id, $context ) {
return $value;
}

/**
* Retrieves user info by user ID, deferring the loading of capability and role data.
*
* This is a lighter-weight alternative to get_userdata() for contexts that mainly need
* display data (name, avatar, bio, etc.), such as author archives and the `$authordata`
* global set up in the Loop.
*
* The `caps`, `roles`, and `allcaps` properties of the returned WP_User are loaded on
* first access, including when calling has_cap() or any of the role or capability methods.
* Until then, they are not included by get_object_vars(), array casts, var_export(), or
* print_r(). Use get_userdata() if that is required.
*
* @since 7.2.0
*
* @param int $user_id User ID.
* @return WP_User|false WP_User object on success, false on failure.
*/
function get_authordata( $user_id ) {
$userdata = WP_User::get_data_by( 'id', $user_id );

if ( ! $userdata ) {
return false;
}

$user = new WP_User();
$user->init( $userdata, 0, true );

return $user;
}

/**
* Updates all user caches.
*
Expand Down
Loading
Loading