PluginProbe
ActivityPub / 2.6.0
ActivityPub v2.6.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
← All changes | includes/model/class-user.php +140 -346 9.2.02.6.0 View file →
@@ -1,27 +1,19 @@
1 1 <?php
2 -/**
3 - * User model file.
4 - *
5 - * @package Activitypub
6 - */
7 -
8 2 namespace Activitypub\Model;
9 3
4 +use WP_Query;
5 +use WP_Error;
6 +use Activitypub\Migration;
7 +use Activitypub\Signature;
8 +use Activitypub\Model\Blog;
10 9 use Activitypub\Activity\Actor;
11 -use Activitypub\Collection\Actors;
12 -use Activitypub\Collection\Extra_Fields;
10 +use Activitypub\Collection\Users;
13 11
14 -use function Activitypub\get_attribution_domains;
12 +use function Activitypub\is_user_disabled;
15 13 use function Activitypub\get_rest_url_by_path;
16 -use function Activitypub\is_blog_public;
17 -use function Activitypub\user_can_activitypub;
14 +use function Activitypub\get_actor_extra_fields;
18 15
19 -/**
20 - * User class.
21 - *
22 - * @method int get__id() Gets the WordPress user ID.
23 - */
24 16 class User extends Actor {
25 17 /**
26 18 * The local User-ID (WP_User).
27 19 *
@@ -29,10 +21,24 @@
29 21 */
30 22 protected $_id; // phpcs:ignore PSR2.Classes.PropertyDeclaration.Underscore
31 23
32 24 /**
33 - * Whether the User is discoverable.
25 + * The Featured-Posts.
34 26 *
27 + * @see https://docs.joinmastodon.org/spec/activitypub/#featured
28 + *
29 + * @context {
30 + * "@id": "http://joinmastodon.org/ns#featured",
31 + * "@type": "@id"
32 + * }
33 + *
34 + * @var string
35 + */
36 + protected $featured;
37 +
38 + /**
39 + * If the User is discoverable.
40 + *
35 41 * @see https://docs.joinmastodon.org/spec/activitypub/#discoverable
36 42 *
37 43 * @context http://joinmastodon.org/ns#discoverable
38 44 *
@@ -40,62 +46,30 @@
40 46 */
41 47 protected $discoverable = true;
42 48
43 49 /**
44 - * The generator of the object.
50 + * If the User is indexable.
45 51 *
46 - * @see https://www.w3.org/TR/activitypub/#generator
47 - * @see https://codeberg.org/fediverse/fep/src/branch/main/fep/844e/fep-844e.md#discovery-through-an-actor
52 + * @context http://joinmastodon.org/ns#indexable
48 53 *
49 - * @var array
54 + * @var boolean
50 55 */
51 - protected $generator = array(
52 - 'type' => 'Application',
53 - 'implements' => array(
54 - array(
55 - 'href' => 'https://datatracker.ietf.org/doc/html/rfc9421',
56 - 'name' => 'RFC-9421: HTTP Message Signatures',
57 - ),
58 - ),
59 - );
56 + protected $indexable;
60 57
61 58 /**
62 - * Constructor.
59 + * The WebFinger Resource.
63 60 *
64 - * @param int $user_id Optional. The WordPress user ID. Default null.
61 + * @var string<url>
65 62 */
66 - public function __construct( $user_id = null ) {
67 - if ( $user_id ) {
68 - $this->_id = $user_id;
63 + protected $webfinger;
69 64
70 - /**
71 - * Fires when a model actor is constructed.
72 - *
73 - * @param User $this The User object.
74 - */
75 - \do_action( 'activitypub_construct_model_actor', $this );
76 - }
77 - }
78 -
79 - /**
80 - * The type of the object.
81 - *
82 - * @return string The type of the object.
83 - */
84 65 public function get_type() {
85 66 return 'Person';
86 67 }
87 68
88 - /**
89 - * Generate a User object from a WP_User.
90 - *
91 - * @param int $user_id The user ID.
92 - *
93 - * @return \WP_Error|User The User object or \WP_Error if user not found.
94 - */
95 69 public static function from_wp_user( $user_id ) {
96 - if ( ! user_can_activitypub( $user_id ) ) {
97 - return new \WP_Error(
70 + if ( is_user_disabled( $user_id ) ) {
71 + return new WP_Error(
98 72 'activitypub_user_not_found',
99 73 \__( 'User not found', 'activitypub' ),
100 74 array( 'status' => 404 )
101 75 );
@@ -100,103 +74,69 @@
100 74 array( 'status' => 404 )
101 75 );
102 76 }
103 77
104 - return new static( $user_id );
78 + $object = new static();
79 + $object->_id = $user_id;
80 +
81 + return $object;
105 82 }
106 83
107 84 /**
108 - * Get the user ID.
85 + * Get the User-ID.
109 86 *
110 - * @return string The user ID.
87 + * @return string The User-ID.
111 88 */
112 89 public function get_id() {
113 - $id = parent::get_id();
114 -
115 - if ( $id ) {
116 - return $id;
117 - }
118 -
119 - $permalink = \get_user_option( 'activitypub_use_permalink_as_id', $this->_id );
120 -
121 - if ( '1' === $permalink ) {
122 - return $this->get_url();
123 - }
124 -
125 - return \add_query_arg( 'author', $this->_id, \home_url( '/' ) );
90 + return $this->get_url();
126 91 }
127 92
128 93 /**
129 - * Get the Username.
94 + * Get the User-Name.
130 95 *
131 - * @return string The Username.
96 + * @return string The User-Name.
132 97 */
133 98 public function get_name() {
134 - return \get_the_author_meta( 'display_name', $this->_id );
99 + return \esc_attr( \get_the_author_meta( 'display_name', $this->_id ) );
135 100 }
136 101
137 102 /**
138 - * Get the User description.
103 + * Get the User-Description.
139 104 *
140 - * @return string The User description.
105 + * @return string The User-Description.
141 106 */
142 107 public function get_summary() {
143 - $description = \get_user_option( 'activitypub_description', $this->_id );
108 + $description = get_user_meta( $this->_id, 'activitypub_user_description', true );
144 109 if ( empty( $description ) ) {
145 - $description = \get_user_meta( $this->_id, 'description', true );
110 + $description = get_user_meta( $this->_id, 'description', true );
146 111 }
147 112 return \wpautop( \wp_kses( $description, 'default' ) );
148 113 }
149 114
150 115 /**
151 - * Get the User url.
116 + * Get the User-Url.
152 117 *
153 - * @return string The User url.
118 + * @return string The User-Url.
154 119 */
155 120 public function get_url() {
156 - return \esc_url_raw( \get_author_posts_url( $this->_id ) );
121 + return \esc_url( \get_author_posts_url( $this->_id ) );
157 122 }
158 123
159 124 /**
160 - * Returns the User URL with @-Prefix for the username.
125 + * Returns the User-URL with @-Prefix for the username.
161 126 *
162 - * @return string The User URL with @-Prefix for the username.
127 + * @return string The User-URL with @-Prefix for the username.
163 128 */
164 129 public function get_alternate_url() {
165 - return \esc_url_raw( \trailingslashit( \get_home_url() ) . '@' . $this->get_preferred_username() );
130 + return \esc_url( \trailingslashit( get_home_url() ) . '@' . $this->get_preferred_username() );
166 131 }
167 132
168 - /**
169 - * Get the preferred username.
170 - *
171 - * @return string The preferred username.
172 - */
173 133 public function get_preferred_username() {
174 - $login = \get_the_author_meta( 'login', $this->_id );
175 -
176 - // Handle cases where login is an email address (e.g., from Site Kit Google login).
177 - if ( \filter_var( $login, FILTER_VALIDATE_EMAIL ) ) {
178 - $login = \get_the_author_meta( 'user_nicename', $this->_id );
179 - }
180 -
181 - return $login;
134 + return \esc_attr( \get_the_author_meta( 'login', $this->_id ) );
182 135 }
183 136
184 - /**
185 - * Get the User icon.
186 - *
187 - * @return string[] The User icon.
188 - */
189 137 public function get_icon() {
190 - $icon = \get_user_option( 'activitypub_icon', $this->_id );
191 - if ( false !== $icon && \wp_attachment_is_image( $icon ) ) {
192 - return array(
193 - 'type' => 'Image',
194 - 'url' => \esc_url_raw( \wp_get_attachment_url( $icon ) ),
195 - );
196 - }
197 -
198 - $icon = \esc_url_raw(
138 + $icon = \esc_url(
199 139 \get_avatar_url(
200 140 $this->_id,
201 141 array( 'size' => 120 )
202 142 )
@@ -207,29 +147,14 @@
207 147 'url' => $icon,
208 148 );
209 149 }
210 150
211 - /**
212 - * Returns the header image.
213 - *
214 - * @return string[]|null The header image.
215 - */
216 151 public function get_image() {
217 - $header_image = \get_user_option( 'activitypub_header_image', $this->_id );
218 - $image_url = null;
219 -
220 - if ( ! $header_image && \has_header_image() ) {
221 - $image_url = \get_header_image();
222 - }
223 -
224 - if ( $header_image ) {
225 - $image_url = \wp_get_attachment_url( $header_image );
226 - }
227 -
228 - if ( $image_url ) {
152 + if ( \has_header_image() ) {
153 + $image = \esc_url( \get_header_image() );
229 154 return array(
230 155 'type' => 'Image',
231 - 'url' => \esc_url_raw( $image_url ),
156 + 'url' => $image,
232 157 );
233 158 }
234 159
235 160 return null;
@@ -234,27 +159,17 @@
234 159
235 160 return null;
236 161 }
237 162
238 - /**
239 - * Returns the date the user was created.
240 - *
241 - * @return false|string The date the user was created.
242 - */
243 163 public function get_published() {
244 - return \gmdate( ACTIVITYPUB_DATE_TIME_RFC3339, \strtotime( \get_the_author_meta( 'registered', $this->_id ) ) );
164 + return \gmdate( 'Y-m-d\TH:i:s\Z', \strtotime( \get_the_author_meta( 'registered', $this->_id ) ) );
245 165 }
246 166
247 - /**
248 - * Returns the public key.
249 - *
250 - * @return string[] The public key.
251 - */
252 167 public function get_public_key() {
253 168 return array(
254 - 'id' => $this->get_id() . '#main-key',
255 - 'owner' => $this->get_id(),
256 - 'publicKeyPem' => Actors::get_public_key( $this->get__id() ),
169 + 'id' => $this->get_id() . '#main-key',
170 + 'owner' => $this->get_id(),
171 + 'publicKeyPem' => Signature::get_public_key_for( $this->get__id() ),
257 172 );
258 173 }
259 174
260 175 /**
@@ -262,9 +177,9 @@
262 177 *
263 178 * @return string The Inbox-Endpoint.
264 179 */
265 180 public function get_inbox() {
266 - return get_rest_url_by_path( \sprintf( 'actors/%d/inbox', $this->get__id() ) );
181 + return get_rest_url_by_path( sprintf( 'actors/%d/inbox', $this->get__id() ) );
267 182 }
268 183
269 184 /**
270 185 * Returns the Outbox-API-Endpoint.
@@ -271,9 +186,9 @@
271 186 *
272 187 * @return string The Outbox-Endpoint.
273 188 */
274 189 public function get_outbox() {
275 - return get_rest_url_by_path( \sprintf( 'actors/%d/outbox', $this->get__id() ) );
190 + return get_rest_url_by_path( sprintf( 'actors/%d/outbox', $this->get__id() ) );
276 191 }
277 192
278 193 /**
279 194 * Returns the Followers-API-Endpoint.
@@ -280,9 +195,9 @@
280 195 *
281 196 * @return string The Followers-Endpoint.
282 197 */
283 198 public function get_followers() {
284 - return get_rest_url_by_path( \sprintf( 'actors/%d/followers', $this->get__id() ) );
199 + return get_rest_url_by_path( sprintf( 'actors/%d/followers', $this->get__id() ) );
285 200 }
286 201
287 202 /**
288 203 * Returns the Following-API-Endpoint.
@@ -289,61 +204,27 @@
289 204 *
290 205 * @return string The Following-Endpoint.
291 206 */
292 207 public function get_following() {
293 - return get_rest_url_by_path( \sprintf( 'actors/%d/following', $this->get__id() ) );
208 + return get_rest_url_by_path( sprintf( 'actors/%d/following', $this->get__id() ) );
294 209 }
295 210
296 211 /**
297 - * Returns the Liked API endpoint.
298 - *
299 - * @since 8.1.0
300 - *
301 - * @return string The Liked endpoint.
302 - */
303 - public function get_liked() {
304 - return get_rest_url_by_path( \sprintf( 'actors/%d/liked', $this->get__id() ) );
305 - }
306 -
307 - /**
308 212 * Returns the Featured-API-Endpoint.
309 213 *
310 214 * @return string The Featured-Endpoint.
311 215 */
312 216 public function get_featured() {
313 - return get_rest_url_by_path( \sprintf( 'actors/%d/collections/featured', $this->get__id() ) );
217 + return get_rest_url_by_path( sprintf( 'actors/%d/collections/featured', $this->get__id() ) );
314 218 }
315 219
316 - /**
317 - * Returns the Featured-Tags-API-Endpoint.
318 - *
319 - * @return string The Featured-Tags-Endpoint.
320 - */
321 - public function get_featured_tags() {
322 - return get_rest_url_by_path( \sprintf( 'actors/%d/collections/tags', $this->get__id() ) );
323 - }
324 -
325 - /**
326 - * Returns the endpoints.
327 - *
328 - * @return string[]|null The endpoints.
329 - */
330 220 public function get_endpoints() {
331 - $endpoints = array(
332 - 'sharedInbox' => get_rest_url_by_path( 'inbox' ),
333 - 'oauthAuthorizationEndpoint' => get_rest_url_by_path( 'oauth/authorize' ),
334 - 'oauthTokenEndpoint' => get_rest_url_by_path( 'oauth/token' ),
335 - 'oauthRegistrationEndpoint' => get_rest_url_by_path( 'oauth/clients' ),
336 - 'proxyUrl' => get_rest_url_by_path( 'proxy' ),
337 - 'proxyEventStream' => get_rest_url_by_path( 'proxy/stream' ),
338 - );
221 + $endpoints = null;
339 222
340 - if ( \get_option( 'activitypub_api', false ) ) {
341 - /*
342 - * RFC 6570 template. add_query_arg() picks the ?/& separator (plain permalinks already
343 - * carry a query string) and does not encode values, so the {q} placeholder stays intact.
344 - */
345 - $endpoints['actorAutocomplete'] = \add_query_arg( 'q', '{q}', get_rest_url_by_path( 'actors/autocomplete' ) );
223 + if ( ACTIVITYPUB_SHARED_INBOX_FEATURE ) {
224 + $endpoints = array(
225 + 'sharedInbox' => get_rest_url_by_path( 'inbox' ),
226 + );
346 227 }
347 228
348 229 return $endpoints;
349 230 }
@@ -353,10 +234,76 @@
353 234 *
354 235 * @return array The extended User-Output.
355 236 */
356 237 public function get_attachment() {
357 - $extra_fields = Extra_Fields::get_actor_fields( $this->_id );
358 - return Extra_Fields::fields_to_attachments( $extra_fields );
238 + $extra_fields = get_actor_extra_fields( \get_current_user_id() );
239 +
240 + $attachments = array();
241 +
242 + foreach ( $extra_fields as $post ) {
243 + $content = \get_the_content( null, false, $post );
244 + $content = \make_clickable( $content );
245 + $content = \do_blocks( $content );
246 + $content = \wptexturize( $content );
247 + $content = \wp_filter_content_tags( $content );
248 + // replace script and style elements
249 + $content = \preg_replace( '@<(script|style)[^>]*?>.*?</\\1>@si', '', $content );
250 + $content = \strip_shortcodes( $content );
251 + $content = \trim( \preg_replace( '/[\n\r\t]/', '', $content ) );
252 +
253 + $attachments[] = array(
254 + 'type' => 'PropertyValue',
255 + 'name' => \get_the_title( $post ),
256 + 'value' => \html_entity_decode(
257 + $content,
258 + \ENT_QUOTES,
259 + 'UTF-8'
260 + ),
261 + );
262 +
263 + $link_added = false;
264 +
265 + // Add support for FEP-fb2a, for more information see FEDERATION.md
266 + if ( \class_exists( '\WP_HTML_Tag_Processor' ) ) {
267 + $tags = new \WP_HTML_Tag_Processor( $content );
268 + $tags->next_tag();
269 +
270 + if ( 'P' === $tags->get_tag() ) {
271 + $tags->next_tag();
272 + }
273 +
274 + if ( 'A' === $tags->get_tag() ) {
275 + $tags->set_bookmark( 'link' );
276 + if ( ! $tags->next_tag() ) {
277 + $tags->seek( 'link' );
278 + $attachment = array(
279 + 'type' => 'Link',
280 + 'name' => \get_the_title( $post ),
281 + 'href' => \esc_url( $tags->get_attribute( 'href' ) ),
282 + 'rel' => explode( ' ', $tags->get_attribute( 'rel' ) ),
283 + );
284 +
285 + $link_added = true;
286 + }
287 + }
288 + }
289 +
290 + if ( ! $link_added ) {
291 + $attachment = array(
292 + 'type' => 'Note',
293 + 'name' => \get_the_title( $post ),
294 + 'content' => \html_entity_decode(
295 + $content,
296 + \ENT_QUOTES,
297 + 'UTF-8'
298 + ),
299 + );
300 + }
301 +
302 + $attachments[] = $attachment;
303 + }
304 +
305 + return $attachments;
359 306 }
360 307
361 308 /**
362 309 * Returns a user@domain type of identifier for the user.
@@ -366,177 +313,24 @@
366 313 public function get_webfinger() {
367 314 return $this->get_preferred_username() . '@' . \wp_parse_url( \home_url(), \PHP_URL_HOST );
368 315 }
369 316
370 - /**
371 - * Returns the canonical URL.
372 - *
373 - * @return string The canonical URL.
374 - */
375 317 public function get_canonical_url() {
376 318 return $this->get_url();
377 319 }
378 320
379 - /**
380 - * Returns the streams.
381 - *
382 - * @return null The streams.
383 - */
384 321 public function get_streams() {
385 322 return null;
386 323 }
387 324
388 - /**
389 - * Returns the tag.
390 - *
391 - * @return array The tag.
392 - */
393 325 public function get_tag() {
394 326 return array();
395 327 }
396 328
397 - /**
398 - * Returns the indexable state.
399 - *
400 - * @return bool Whether the user is indexable.
401 - */
402 329 public function get_indexable() {
403 - if ( is_blog_public() ) {
330 + if ( \get_option( 'blog_public', 1 ) ) {
404 331 return true;
405 332 } else {
406 333 return false;
407 - }
408 - }
409 -
410 - /**
411 - * Update the username.
412 - *
413 - * @param string $value The new value.
414 - * @return int|\WP_Error The updated user ID or \WP_Error on failure.
415 - */
416 - public function update_name( $value ) {
417 - $userdata = array(
418 - 'ID' => $this->_id,
419 - 'display_name' => $value,
420 - );
421 - return \wp_update_user( $userdata );
422 - }
423 -
424 - /**
425 - * Update the User description.
426 - *
427 - * @param string $value The new value.
428 - * @return bool True if the attribute was updated, false otherwise.
429 - */
430 - public function update_summary( $value ) {
431 - return \update_user_option( $this->_id, 'activitypub_description', $value );
432 - }
433 -
434 - /**
435 - * Update the User icon.
436 - *
437 - * @param int $value The new value. Should be an attachment ID.
438 - * @return bool True if the attribute was updated, false otherwise.
439 - */
440 - public function update_icon( $value ) {
441 - if ( ! \wp_attachment_is_image( $value ) ) {
442 - return false;
443 - }
444 - return \update_user_option( $this->_id, 'activitypub_icon', $value );
445 - }
446 -
447 - /**
448 - * Update the User-Header-Image.
449 - *
450 - * @param int $value The new value. Should be an attachment ID.
451 - * @return bool True if the attribute was updated, false otherwise.
452 - */
453 - public function update_header( $value ) {
454 - if ( ! \wp_attachment_is_image( $value ) ) {
455 - return false;
456 - }
457 - return \update_user_option( $this->_id, 'activitypub_header_image', $value );
458 - }
459 -
460 - /**
461 - * Returns the website hosts allowed to credit this blog.
462 - *
463 - * @return string[]|null The attribution domains or null if not found.
464 - */
465 - public function get_attribution_domains() {
466 - return get_attribution_domains();
467 - }
468 -
469 - /**
470 - * Returns the alsoKnownAs.
471 - *
472 - * @return string[] The alsoKnownAs.
473 - */
474 - public function get_also_known_as() {
475 - $also_known_as = array(
476 - \add_query_arg( 'author', $this->_id, \home_url( '/' ) ),
477 - $this->get_url(),
478 - $this->get_alternate_url(),
479 - );
480 -
481 - $also_known_as = \array_merge( $also_known_as, \get_user_option( 'activitypub_also_known_as', $this->_id ) ?: array() );
482 -
483 - return \array_unique( $also_known_as );
484 - }
485 -
486 - /**
487 - * Returns the movedTo.
488 - *
489 - * @return string The movedTo.
490 - */
491 - public function get_moved_to() {
492 - $moved_to = \get_user_option( 'activitypub_moved_to', $this->_id );
493 -
494 - return $moved_to && $moved_to !== $this->get_id() ? $moved_to : null;
495 - }
496 -
497 - /**
498 - * Get the actor-level interaction policy.
499 - *
500 - * Overrides the magic property accessor on Base_Object so that we always
501 - * compute the policy from the current site setting rather than returning a
502 - * cached property value. Currently only emits `canFeature` (FEP-7aa9).
503 - * Driven by the site option `activitypub_default_feature_policy` and
504 - * defaults to denying all featured-collection requests, in line with
505 - * FEP-7aa9's "absence of policy = no consent" rule.
506 - *
507 - * @see https://w3id.org/fep/7aa9
508 - *
509 - * @since 9.0.0
510 - *
511 - * @return array
512 - */
513 - public function get_interaction_policy() {
514 - $policy = array( 'canFeature' => $this->build_can_feature_policy() );
515 -
516 - // Merge with an explicitly set interaction policy, if any.
517 - if ( $this->interaction_policy ) {
518 - $policy = \array_merge( (array) $this->interaction_policy, $policy );
519 - }
520 -
521 - return $policy;
522 - }
523 -
524 - /**
525 - * Build the `canFeature` policy array from the site option.
526 - *
527 - * @return array
528 - */
529 - protected function build_can_feature_policy() {
530 - $policy = \get_option( 'activitypub_default_feature_policy', ACTIVITYPUB_INTERACTION_POLICY_ME );
531 -
532 - switch ( $policy ) {
533 - case ACTIVITYPUB_INTERACTION_POLICY_ANYONE:
534 - return array( 'automaticApproval' => array( 'https://www.w3.org/ns/activitystreams#Public' ) );
535 - case ACTIVITYPUB_INTERACTION_POLICY_FOLLOWERS:
536 - return array( 'automaticApproval' => array( $this->get_followers() ) );
537 - case ACTIVITYPUB_INTERACTION_POLICY_ME:
538 - default:
539 - return array( 'automaticApproval' => array( $this->get_id() ) );
540 334 }
541 335 }
542 336 }