PluginProbe
ActivityPub / 9.0.0
ActivityPub v9.0.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 9.0.0, at includes/functions-user.php

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