PluginProbe
ActivityPub / trunk
ActivityPub vtrunk
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 trunk, at includes/functions-user.php

379 lines 9.8 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::BLOG_USER_ID:
97 $enabled = ! is_user_type_disabled( 'blog' );
98 break;
99
100 default:
101 if ( ! \get_user_by( 'id', $user_id ) ) {
102 $enabled = false;
103 break;
104 }
105
106 if ( is_user_type_disabled( 'user' ) ) {
107 $enabled = false;
108 break;
109 }
110
111 $enabled = \user_can( $user_id, 'activitypub' );
112 }
113
114 /**
115 * Allow plugins to enable/disable users for ActivityPub.
116 *
117 * @param boolean $enabled True if the user is enabled, false otherwise.
118 * @param int $user_id The user ID.
119 */
120 return \apply_filters( 'activitypub_user_can_activitypub', $enabled, $user_id );
121 }
122
123 /**
124 * Whether the current user is allowed to act on behalf of the blog actor.
125 *
126 * The blog actor is virtual (no `wp_users` row), so ownership and authoring
127 * checks against `BLOG_USER_ID = 0` cannot rely on identity equality. This
128 * helper centralizes the "can the current user post / read as the blog?"
129 * decision: administrators by default, filterable for integrations.
130 *
131 * @since 8.3.0
132 *
133 * @return bool True if the current user can act as the blog actor.
134 */
135 function user_can_act_as_blog() {
136 /**
137 * Filters whether the current user is allowed to act as the blog actor.
138 *
139 * Defaults to true for users with the `manage_options` capability (administrators).
140 * Filter to broaden the allow-list, for example to editors on multi-author sites.
141 *
142 * Security note: returning a static `true` (e.g. via `__return_true`) grants
143 * EVERY authenticated user the right to post as, read private outbox items of,
144 * and view stats for the blog actor. Always inspect the current user inside
145 * the callback (`current_user_can()`, role, allowlist) before returning `true`.
146 *
147 * @since 8.3.0
148 *
149 * @param bool $can_act_as_blog Whether the current user can act as the blog actor.
150 */
151 return (bool) \apply_filters( 'activitypub_user_can_act_as_blog', \current_user_can( 'manage_options' ) );
152 }
153
154 /**
155 * Checks if a User-Type is disabled for ActivityPub.
156 *
157 * This function is used to check if the 'blog' or 'user'
158 * type is disabled for ActivityPub.
159 *
160 * @param string $type User type. 'blog' or 'user'.
161 *
162 * @return boolean True if the user type is disabled, false otherwise.
163 */
164 function is_user_type_disabled( $type ) {
165 switch ( $type ) {
166 case 'blog':
167 if ( \defined( 'ACTIVITYPUB_SINGLE_USER_MODE' ) ) {
168 if ( ACTIVITYPUB_SINGLE_USER_MODE ) {
169 $disabled = false;
170 break;
171 }
172 }
173
174 if ( \defined( 'ACTIVITYPUB_DISABLE_BLOG_USER' ) ) {
175 $disabled = ACTIVITYPUB_DISABLE_BLOG_USER;
176 break;
177 }
178
179 if ( ACTIVITYPUB_ACTOR_MODE === \get_option( 'activitypub_actor_mode', ACTIVITYPUB_ACTOR_MODE ) ) {
180 $disabled = true;
181 break;
182 }
183
184 $disabled = false;
185 break;
186 case 'user':
187 if ( \defined( 'ACTIVITYPUB_SINGLE_USER_MODE' ) ) {
188 if ( ACTIVITYPUB_SINGLE_USER_MODE ) {
189 $disabled = true;
190 break;
191 }
192 }
193
194 if ( \defined( 'ACTIVITYPUB_DISABLE_USER' ) ) {
195 $disabled = ACTIVITYPUB_DISABLE_USER;
196 break;
197 }
198
199 if ( ACTIVITYPUB_BLOG_MODE === \get_option( 'activitypub_actor_mode', ACTIVITYPUB_ACTOR_MODE ) ) {
200 $disabled = true;
201 break;
202 }
203
204 $disabled = false;
205 break;
206 default:
207 // Treat unknown user types as disabled to ensure a consistent boolean return value.
208 $disabled = true;
209 break;
210 }
211
212 /**
213 * Allow plugins to disable user types for ActivityPub.
214 *
215 * @param boolean $disabled True if the user type is disabled, false otherwise.
216 * @param string $type The User-Type.
217 */
218 return \apply_filters( 'activitypub_is_user_type_disabled', $disabled, $type );
219 }
220
221 /**
222 * Check if the blog is in single-user mode.
223 *
224 * @return boolean True if the blog is in single-user mode, false otherwise.
225 */
226 function is_single_user() {
227 if (
228 false === is_user_type_disabled( 'blog' ) &&
229 true === is_user_type_disabled( 'user' )
230 ) {
231 return true;
232 }
233
234 return false;
235 }
236
237 /**
238 * Get active users based on a given duration.
239 *
240 * Counts users who published posts (of any ActivityPub-enabled post type)
241 * or approved comments within the given time period.
242 *
243 * @param int $duration Optional. The duration to check in month(s). Default 1.
244 *
245 * @return int The number of active users.
246 */
247 function get_active_users( $duration = 1 ) {
248 $duration = \intval( $duration );
249 $transient_key = \sprintf( 'monthly_active_users_%d', $duration );
250 $count = \get_transient( $transient_key );
251
252 if ( false === $count ) {
253 global $wpdb;
254
255 $post_types = \get_post_types_by_support( 'activitypub' );
256 $post_authors = array();
257
258 if ( ! empty( $post_types ) ) {
259 $placeholders = \implode( ', ', \array_fill( 0, \count( $post_types ), '%s' ) );
260
261 // Get distinct user IDs who published posts of AP-enabled post types.
262 // phpcs:ignore WordPress.DB.DirectDatabaseQuery
263 $post_authors = $wpdb->get_col(
264 $wpdb->prepare(
265 // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
266 "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 )",
267 \array_merge( $post_types, array( $duration ) )
268 )
269 );
270 }
271
272 // Get distinct user IDs who made approved comments.
273 // phpcs:ignore WordPress.DB.DirectDatabaseQuery
274 $comment_authors = $wpdb->get_col(
275 $wpdb->prepare(
276 "SELECT DISTINCT user_id FROM {$wpdb->comments} WHERE comment_approved = '1' AND user_id != 0 AND comment_date >= DATE_SUB( NOW(), INTERVAL %d MONTH )",
277 $duration
278 )
279 );
280
281 // Deduplicate and filter out anonymous (0) entries.
282 $active_ids = \array_unique( \array_filter( \array_map( 'absint', \array_merge( $post_authors, $comment_authors ) ) ) );
283
284 if ( empty( $active_ids ) ) {
285 $count = 0;
286 } else {
287 // Count only users who have the activitypub capability.
288 $user_query = new \WP_User_Query(
289 array(
290 'capability__in' => array( 'activitypub' ),
291 'include' => $active_ids,
292 'number' => 1, // Minimize memory; get_total() still returns full count.
293 )
294 );
295 $count = $user_query->get_total();
296 }
297
298 \set_transient( $transient_key, $count, DAY_IN_SECONDS );
299 }
300
301 // If 0 authors were active.
302 if ( 0 === (int) $count ) {
303 return 0;
304 }
305
306 // If single user mode.
307 if ( is_single_user() ) {
308 return 1;
309 }
310
311 // If blog user is disabled.
312 if ( ! user_can_activitypub( Actors::BLOG_USER_ID ) ) {
313 $active = (int) $count;
314 } else {
315 // Also count blog user.
316 $active = (int) $count + 1;
317 }
318
319 // Ensure active users doesn't exceed total users.
320 return \min( $active, get_total_users() );
321 }
322
323 /**
324 * Get the total number of users.
325 *
326 * @return int The total number of users.
327 */
328 function get_total_users() {
329 // If single user mode.
330 if ( is_single_user() ) {
331 return 1;
332 }
333
334 $user_query = new \WP_User_Query(
335 array(
336 'capability__in' => array( 'activitypub' ),
337 'number' => 1,
338 )
339 );
340
341 $users = $user_query->get_total();
342
343 // If blog user is disabled.
344 if ( ! user_can_activitypub( Actors::BLOG_USER_ID ) ) {
345 return (int) $users;
346 }
347
348 return (int) $users + 1;
349 }
350
351 /**
352 * Get the ActivityPub ID of a User by the WordPress User ID.
353 *
354 * Fall back to blog user if in blog mode or if user is not found.
355 *
356 * @param int $id The WordPress User ID.
357 *
358 * @return string|false The ActivityPub ID (a URL) of the User or false if not found.
359 */
360 function get_user_id( $id ) {
361 $mode = \get_option( 'activitypub_actor_mode', 'default' );
362
363 if ( ACTIVITYPUB_BLOG_MODE === $mode ) {
364 $user = Actors::get_by_id( Actors::BLOG_USER_ID );
365 } else {
366 $user = Actors::get_by_id( $id );
367
368 if ( \is_wp_error( $user ) ) {
369 $user = Actors::get_by_id( Actors::BLOG_USER_ID );
370 }
371 }
372
373 if ( \is_wp_error( $user ) ) {
374 return false;
375 }
376
377 return $user->get_id();
378 }
379