PluginProbe
ActivityPub / 8.3.0
ActivityPub v8.3.0
9.3.1 9.3.0 9.2.2 9.2.1 9.2.0 9.1.0 9.0.2 9.0.1 9.0.0 8.3.0 8.2.1 8.2.0 8.1.1 1.0.5 1.0.6 1.0.7 1.0.8 1.0.9 1.1.0 1.2.0 1.3.0 2.0.0 2.0.1 2.1.0 2.1.1 All 160 releases
activitypub / includes / functions-user.php

functions-user.php in ActivityPub 8.3.0, at includes/functions-user.php

398 lines 10.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * User functions.
4 *
5 * Functions for working with users and actors in ActivityPub context.
6 *
7 * @package Activitypub
8 */
9
10 namespace Activitypub;
11
12 use Activitypub\Collection\Actors;
13 use Activitypub\Collection\Followers;
14
15 /**
16 * Returns a users WebFinger "resource".
17 *
18 * @deprecated 7.1.0 Use {@see \Activitypub\Webfinger::get_user_resource} instead.
19 *
20 * @param int $user_id The user ID.
21 *
22 * @return string The User resource.
23 */
24 function get_webfinger_resource( $user_id ) {
25 \_deprecated_function( __FUNCTION__, '7.1.0', 'Activitypub\Webfinger::get_user_resource' );
26
27 return Webfinger::get_user_resource( $user_id );
28 }
29
30 /**
31 * Returns the followers of a given user.
32 *
33 * @param int $user_id The user ID.
34 *
35 * @return array The followers.
36 */
37 function get_followers( $user_id ) {
38 return Followers::get_many( $user_id );
39 }
40
41 /**
42 * Count the number of followers for a given user.
43 *
44 * @param int $user_id The user ID.
45 *
46 * @return int The number of followers.
47 */
48 function count_followers( $user_id ) {
49 return Followers::count( $user_id );
50 }
51
52 /**
53 * Examine a url and try to determine the author ID it represents.
54 *
55 * Checks are supposedly from the hosted site blog.
56 *
57 * @param string $url Permalink to check.
58 *
59 * @return int|null User ID, or null on failure.
60 */
61 function url_to_authorid( $url ) {
62 global $wp_rewrite;
63
64 // Check if url has the same host.
65 $request_host = \wp_parse_url( $url, \PHP_URL_HOST );
66 if ( \wp_parse_url( \home_url(), \PHP_URL_HOST ) !== $request_host && get_option( 'activitypub_old_host' ) !== $request_host ) {
67 return null;
68 }
69
70 // First, check to see if there is an 'author=N' to match against.
71 if ( \preg_match( '/[?&]author=(\d+)/i', $url, $values ) ) {
72 return \absint( $values[1] );
73 }
74
75 // Check to see if we are using rewrite rules.
76 $rewrite = $wp_rewrite->wp_rewrite_rules();
77
78 // Not using rewrite rules, and 'author=N' method failed, so we're out of options.
79 if ( empty( $rewrite ) ) {
80 return null;
81 }
82
83 // Generate rewrite rule for the author url.
84 $author_rewrite = $wp_rewrite->get_author_permastruct();
85 $author_regexp = \str_replace( '%author%', '', $author_rewrite );
86
87 // Match the rewrite rule with the passed url.
88 if ( \preg_match( '/https?:\/\/(.+)' . \preg_quote( $author_regexp, '/' ) . '([^\/]+)/i', $url, $match ) ) {
89 $user = \get_user_by( 'slug', $match[2] );
90 if ( $user ) {
91 return $user->ID;
92 }
93 }
94
95 return null;
96 }
97
98 /**
99 * This function checks if a user is enabled for ActivityPub.
100 *
101 * @param int|string $user_id The user ID.
102 *
103 * @return boolean True if the user is enabled, false otherwise.
104 */
105 function user_can_activitypub( $user_id ) {
106 if ( ! is_numeric( $user_id ) ) {
107 return false;
108 }
109
110 switch ( $user_id ) {
111 case Actors::APPLICATION_USER_ID:
112 $enabled = true; // Application user is always enabled.
113 break;
114
115 case Actors::BLOG_USER_ID:
116 $enabled = ! is_user_type_disabled( 'blog' );
117 break;
118
119 default:
120 if ( ! \get_user_by( 'id', $user_id ) ) {
121 $enabled = false;
122 break;
123 }
124
125 if ( is_user_type_disabled( 'user' ) ) {
126 $enabled = false;
127 break;
128 }
129
130 $enabled = \user_can( $user_id, 'activitypub' );
131 }
132
133 /**
134 * Allow plugins to enable/disable users for ActivityPub.
135 *
136 * @param boolean $enabled True if the user is enabled, false otherwise.
137 * @param int $user_id The user ID.
138 */
139 return apply_filters( 'activitypub_user_can_activitypub', $enabled, $user_id );
140 }
141
142 /**
143 * Whether the current user is allowed to act on behalf of the blog actor.
144 *
145 * The blog actor is virtual (no `wp_users` row), so ownership and authoring
146 * checks against `BLOG_USER_ID = 0` cannot rely on identity equality. This
147 * helper centralizes the "can the current user post / read as the blog?"
148 * decision: administrators by default, filterable for integrations.
149 *
150 * @since 8.3.0
151 *
152 * @return bool True if the current user can act as the blog actor.
153 */
154 function user_can_act_as_blog() {
155 /**
156 * Filters whether the current user is allowed to act as the blog actor.
157 *
158 * Defaults to true for users with the `manage_options` capability (administrators).
159 * Filter to broaden the allow-list, for example to editors on multi-author sites.
160 *
161 * Security note: returning a static `true` (e.g. via `__return_true`) grants
162 * EVERY authenticated user the right to post as, read private outbox items of,
163 * and view stats for the blog actor. Always inspect the current user inside
164 * the callback (`current_user_can()`, role, allowlist) before returning `true`.
165 *
166 * @since 8.3.0
167 *
168 * @param bool $can_act_as_blog Whether the current user can act as the blog actor.
169 */
170 return (bool) \apply_filters( 'activitypub_user_can_act_as_blog', \current_user_can( 'manage_options' ) );
171 }
172
173 /**
174 * Checks if a User-Type is disabled for ActivityPub.
175 *
176 * This function is used to check if the 'blog' or 'user'
177 * type is disabled for ActivityPub.
178 *
179 * @param string $type User type. 'blog' or 'user'.
180 *
181 * @return boolean True if the user type is disabled, false otherwise.
182 */
183 function is_user_type_disabled( $type ) {
184 switch ( $type ) {
185 case 'blog':
186 if ( \defined( 'ACTIVITYPUB_SINGLE_USER_MODE' ) ) {
187 if ( ACTIVITYPUB_SINGLE_USER_MODE ) {
188 $disabled = false;
189 break;
190 }
191 }
192
193 if ( \defined( 'ACTIVITYPUB_DISABLE_BLOG_USER' ) ) {
194 $disabled = ACTIVITYPUB_DISABLE_BLOG_USER;
195 break;
196 }
197
198 if ( ACTIVITYPUB_ACTOR_MODE === \get_option( 'activitypub_actor_mode', ACTIVITYPUB_ACTOR_MODE ) ) {
199 $disabled = true;
200 break;
201 }
202
203 $disabled = false;
204 break;
205 case 'user':
206 if ( \defined( 'ACTIVITYPUB_SINGLE_USER_MODE' ) ) {
207 if ( ACTIVITYPUB_SINGLE_USER_MODE ) {
208 $disabled = true;
209 break;
210 }
211 }
212
213 if ( \defined( 'ACTIVITYPUB_DISABLE_USER' ) ) {
214 $disabled = ACTIVITYPUB_DISABLE_USER;
215 break;
216 }
217
218 if ( ACTIVITYPUB_BLOG_MODE === \get_option( 'activitypub_actor_mode', ACTIVITYPUB_ACTOR_MODE ) ) {
219 $disabled = true;
220 break;
221 }
222
223 $disabled = false;
224 break;
225 default:
226 // Treat unknown user types as disabled to ensure a consistent boolean return value.
227 $disabled = true;
228 break;
229 }
230
231 /**
232 * Allow plugins to disable user types for ActivityPub.
233 *
234 * @param boolean $disabled True if the user type is disabled, false otherwise.
235 * @param string $type The User-Type.
236 */
237 return apply_filters( 'activitypub_is_user_type_disabled', $disabled, $type );
238 }
239
240 /**
241 * Check if the blog is in single-user mode.
242 *
243 * @return boolean True if the blog is in single-user mode, false otherwise.
244 */
245 function is_single_user() {
246 if (
247 false === is_user_type_disabled( 'blog' ) &&
248 true === is_user_type_disabled( 'user' )
249 ) {
250 return true;
251 }
252
253 return false;
254 }
255
256 /**
257 * Get active users based on a given duration.
258 *
259 * Counts users who published posts (of any ActivityPub-enabled post type)
260 * or approved comments within the given time period.
261 *
262 * @param int $duration Optional. The duration to check in month(s). Default 1.
263 *
264 * @return int The number of active users.
265 */
266 function get_active_users( $duration = 1 ) {
267 $duration = \intval( $duration );
268 $transient_key = \sprintf( 'monthly_active_users_%d', $duration );
269 $count = \get_transient( $transient_key );
270
271 if ( false === $count ) {
272 global $wpdb;
273
274 $post_types = \get_post_types_by_support( 'activitypub' );
275 $post_authors = array();
276
277 if ( ! empty( $post_types ) ) {
278 $placeholders = \implode( ', ', \array_fill( 0, \count( $post_types ), '%s' ) );
279
280 // Get distinct user IDs who published posts of AP-enabled post types.
281 // phpcs:ignore WordPress.DB.DirectDatabaseQuery
282 $post_authors = $wpdb->get_col(
283 $wpdb->prepare(
284 // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
285 "SELECT DISTINCT post_author FROM {$wpdb->posts} WHERE post_type IN ( {$placeholders} ) AND post_status = 'publish' AND post_date >= DATE_SUB( NOW(), INTERVAL %d MONTH )",
286 \array_merge( $post_types, array( $duration ) )
287 )
288 );
289 }
290
291 // Get distinct user IDs who made approved comments.
292 // phpcs:ignore WordPress.DB.DirectDatabaseQuery
293 $comment_authors = $wpdb->get_col(
294 $wpdb->prepare(
295 "SELECT DISTINCT user_id FROM {$wpdb->comments} WHERE comment_approved = '1' AND user_id != 0 AND comment_date >= DATE_SUB( NOW(), INTERVAL %d MONTH )",
296 $duration
297 )
298 );
299
300 // Deduplicate and filter out anonymous (0) entries.
301 $active_ids = \array_unique( \array_filter( \array_map( 'absint', \array_merge( $post_authors, $comment_authors ) ) ) );
302
303 if ( empty( $active_ids ) ) {
304 $count = 0;
305 } else {
306 // Count only users who have the activitypub capability.
307 $user_query = new \WP_User_Query(
308 array(
309 'capability__in' => array( 'activitypub' ),
310 'include' => $active_ids,
311 'number' => 1, // Minimize memory; get_total() still returns full count.
312 )
313 );
314 $count = $user_query->get_total();
315 }
316
317 \set_transient( $transient_key, $count, DAY_IN_SECONDS );
318 }
319
320 // If 0 authors were active.
321 if ( 0 === (int) $count ) {
322 return 0;
323 }
324
325 // If single user mode.
326 if ( is_single_user() ) {
327 return 1;
328 }
329
330 // If blog user is disabled.
331 if ( ! user_can_activitypub( Actors::BLOG_USER_ID ) ) {
332 $active = (int) $count;
333 } else {
334 // Also count blog user.
335 $active = (int) $count + 1;
336 }
337
338 // Ensure active users doesn't exceed total users.
339 return \min( $active, get_total_users() );
340 }
341
342 /**
343 * Get the total number of users.
344 *
345 * @return int The total number of users.
346 */
347 function get_total_users() {
348 // If single user mode.
349 if ( is_single_user() ) {
350 return 1;
351 }
352
353 $user_query = new \WP_User_Query(
354 array(
355 'capability__in' => array( 'activitypub' ),
356 'number' => 1,
357 )
358 );
359
360 $users = $user_query->get_total();
361
362 // If blog user is disabled.
363 if ( ! user_can_activitypub( Actors::BLOG_USER_ID ) ) {
364 return (int) $users;
365 }
366
367 return (int) $users + 1;
368 }
369
370 /**
371 * Get the ActivityPub ID of a User by the WordPress User ID.
372 *
373 * Fall back to blog user if in blog mode or if user is not found.
374 *
375 * @param int $id The WordPress User ID.
376 *
377 * @return string|false The ActivityPub ID (a URL) of the User or false if not found.
378 */
379 function get_user_id( $id ) {
380 $mode = \get_option( 'activitypub_actor_mode', 'default' );
381
382 if ( ACTIVITYPUB_BLOG_MODE === $mode ) {
383 $user = Actors::get_by_id( Actors::BLOG_USER_ID );
384 } else {
385 $user = Actors::get_by_id( $id );
386
387 if ( \is_wp_error( $user ) ) {
388 $user = Actors::get_by_id( Actors::BLOG_USER_ID );
389 }
390 }
391
392 if ( \is_wp_error( $user ) ) {
393 return false;
394 }
395
396 return $user->get_id();
397 }
398