PluginProbe
WPGraphQL / 2.22.1
WPGraphQL v2.22.1
2.22.3 2.22.2 2.22.1 2.22.0 2.21.1 2.21.0 2.20.0 2.19.0 2.18.0 2.17.0 2.16.0 2.15.1 2.15.0 2.14.1 2.14.0 2.13.0 2.2.0 2.3.0 2.3.3 2.3.6 2.3.8 2.5.0 2.5.1 2.5.2 2.5.3 All 177 releases
wp-graphql / src / Data / Loader / UserLoader.php

UserLoader.php in WPGraphQL 2.22.1, at src/Data/Loader/UserLoader.php

241 lines 6.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 namespace WPGraphQL\Data\Loader;
3
4 use WPGraphQL\Model\User;
5
6 /**
7 * Class UserLoader
8 *
9 * @package WPGraphQL\Data\Loader
10 */
11 class UserLoader extends AbstractDataLoader {
12
13 /**
14 * {@inheritDoc}
15 *
16 * @param mixed|\WP_User $entry The User object
17 *
18 * @return ?\WPGraphQL\Model\User
19 * @throws \Exception
20 */
21 protected function get_model( $entry, $key ) {
22 if ( $entry instanceof \WP_User ) {
23 return new User( $entry );
24 } else {
25 return null;
26 }
27 }
28
29 /**
30 * Normalize a loader key to a WordPress user database ID.
31 *
32 * Only non-empty digit-only strings and positive integers are accepted so values
33 * such as "0) OR …" cannot pass `absint`-based checks elsewhere and reach SQL.
34 *
35 * @param mixed $key Loader key (typically an integer or numeric string).
36 *
37 * @return int|null Positive user ID, or null if the key is not a valid ID.
38 */
39 private function parse_user_database_id( $key ): ?int {
40 if ( is_int( $key ) ) {
41 return $key > 0 ? $key : null;
42 }
43
44 if ( is_string( $key ) ) {
45 if ( '' === $key || ! ctype_digit( $key ) ) {
46 return null;
47 }
48 $id = absint( $key );
49
50 return $id > 0 ? $id : null;
51 }
52
53 return null;
54 }
55
56 /**
57 * The data loader always returns a user object if it exists, but we need to
58 * separately determine whether the user should be considered private. The
59 * WordPress frontend does not expose authors without published posts, so our
60 * privacy model follows that same convention.
61 *
62 * Example return format for input "[ 1, 2 ]":
63 *
64 * [
65 * 2 => true, // User 2 is public (has published posts)
66 * ]
67 *
68 * In this example, user 1 is not public (has no published posts) and is
69 * omitted from the returned array.
70 *
71 * @param int[] $keys Array of author IDs (int).
72 *
73 * @return array<int,bool> Associative array of author IDs (int) to boolean.
74 */
75 public function get_public_users( array $keys ) {
76 $sanitized_keys = [];
77 foreach ( $keys as $key ) {
78 $id = $this->parse_user_database_id( $key );
79 if ( null !== $id ) {
80 $sanitized_keys[] = $id;
81 }
82 }
83 $sanitized_keys = array_values( array_unique( $sanitized_keys ) );
84
85 if ( empty( $sanitized_keys ) ) {
86 return [];
87 }
88
89 $keys = $sanitized_keys;
90
91 // Get public post types that are set to show in GraphQL
92 // as public users are determined by whether they've published
93 // content in one of these post types
94 $post_types = \WPGraphQL::get_allowed_post_types(
95 'names',
96 [
97 'public' => true,
98 ]
99 );
100
101 /**
102 * Exclude revisions and attachments, since neither ever receive the
103 * "publish" post status.
104 */
105 unset( $post_types['revision'], $post_types['attachment'] );
106
107 /**
108 * Only retrieve public posts by the provided author IDs. Also,
109 * get_posts_by_author_sql only accepts a single author ID, so we'll need to
110 * add our own IN statement.
111 */
112 $author_id = null;
113 $public_only = true;
114
115 $where = get_posts_by_author_sql( $post_types, true, $author_id, $public_only );
116 $ids = implode( ', ', $keys );
117
118 global $wpdb;
119
120 $results = $wpdb->get_results( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
121 $wpdb->prepare(
122 "SELECT DISTINCT $wpdb->users.ID FROM $wpdb->posts INNER JOIN $wpdb->users ON post_author = $wpdb->users.ID $where AND post_author IN ( %1\$s ) ORDER BY FIELD( $wpdb->users.ID, %2\$s)", // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared,WordPress.DB.PreparedSQLPlaceholders.UnquotedComplexPlaceholder,WordPressVIPMinimum.Variables.RestrictedVariables.user_meta__wpdb__users
123 $ids,
124 $ids
125 )
126 );
127
128 /**
129 * Empty results or error.
130 */
131 if ( ! is_array( $results ) ) {
132 return [];
133 }
134
135 /**
136 * Reduce to an associative array that can be easily consumed.
137 */
138 return array_reduce(
139 $results,
140 static function ( $carry, $result ) {
141 $carry[ (int) $result->ID ] = true;
142 return $carry;
143 },
144 []
145 );
146 }
147
148 /**
149 * {@inheritDoc}
150 *
151 * @param int[] $keys
152 *
153 * @return array<int,\WP_User|null>
154 */
155 public function loadKeys( array $keys ) {
156 if ( empty( $keys ) ) {
157 return $keys;
158 }
159
160 $key_to_id = [];
161 foreach ( $keys as $key ) {
162 $key_to_id[ $key ] = $this->parse_user_database_id( $key );
163 }
164
165 $valid_ids = array_values(
166 array_unique(
167 array_filter(
168 array_values( $key_to_id ),
169 static function ( $id ) {
170 return null !== $id;
171 }
172 )
173 )
174 );
175
176 if ( empty( $valid_ids ) ) {
177 return array_fill_keys( $keys, null );
178 }
179
180 /**
181 * Prepare the args for the query. We're provided a specific
182 * set of IDs, so we want to query as efficiently as possible with
183 * as little overhead as possible. We don't want to return post counts,
184 * we don't want to include sticky posts, and we want to limit the query
185 * to the count of the keys provided. We don't care about the order since we
186 * will reorder them ourselves to match the order of the provided keys.
187 */
188 $args = [
189 'include' => $valid_ids,
190 'number' => count( $valid_ids ),
191 'count_total' => false,
192 'fields' => 'all_with_meta',
193 ];
194
195 /**
196 * Query for the users and get the results
197 */
198 $query = new \WP_User_Query( $args );
199 $query->get_results();
200
201 /**
202 * Determine which of the users are public (have published posts).
203 */
204 $public_users = $this->get_public_users( $valid_ids );
205
206 /**
207 * Loop over the keys and reduce to an associative array, providing the
208 * WP_User instance (if found) or null. This ensures that the returned array
209 * has the same keys that were provided and in the same order.
210 */
211 return array_reduce(
212 $keys,
213 static function ( $carry, $key ) use ( $public_users, $key_to_id ) {
214 $user_id = $key_to_id[ $key ] ?? null;
215
216 if ( null === $user_id ) {
217 $carry[ $key ] = null;
218 return $carry;
219 }
220
221 $user = get_user_by( 'id', $user_id ); // Cached via previous WP_User_Query.
222
223 if ( $user instanceof \WP_User ) {
224 /**
225 * Set a property on the user that can be accessed by the User model.
226 */
227 // @phpstan-ignore-next-line
228 $user->is_private = ! isset( $public_users[ $user_id ] );
229
230 $carry[ $key ] = $user;
231 } else {
232 $carry[ $key ] = null;
233 }
234
235 return $carry;
236 },
237 []
238 );
239 }
240 }
241