PluginProbe
ActivityPub / 3.2.4
ActivityPub v3.2.4
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 +74 -335 9.2.23.2.4 View file →
@@ -1,27 +1,20 @@
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;
10 +use Activitypub\Collection\Users;
12 11 use Activitypub\Collection\Extra_Fields;
13 12
14 -use function Activitypub\get_attribution_domains;
13 +use function Activitypub\is_blog_public;
14 +use function Activitypub\is_user_disabled;
15 15 use function Activitypub\get_rest_url_by_path;
16 -use function Activitypub\is_blog_public;
17 -use function Activitypub\user_can_activitypub;
18 16
19 -/**
20 - * User class.
21 - *
22 - * @method int get__id() Gets the WordPress user ID.
23 - */
24 17 class User extends Actor {
25 18 /**
26 19 * The local User-ID (WP_User).
27 20 *
@@ -29,10 +22,24 @@
29 22 */
30 23 protected $_id; // phpcs:ignore PSR2.Classes.PropertyDeclaration.Underscore
31 24
32 25 /**
33 - * Whether the User is discoverable.
26 + * The Featured-Posts.
34 27 *
28 + * @see https://docs.joinmastodon.org/spec/activitypub/#featured
29 + *
30 + * @context {
31 + * "@id": "http://joinmastodon.org/ns#featured",
32 + * "@type": "@id"
33 + * }
34 + *
35 + * @var string
36 + */
37 + protected $featured;
38 +
39 + /**
40 + * If the User is discoverable.
41 + *
35 42 * @see https://docs.joinmastodon.org/spec/activitypub/#discoverable
36 43 *
37 44 * @context http://joinmastodon.org/ns#discoverable
38 45 *
@@ -40,62 +47,30 @@
40 47 */
41 48 protected $discoverable = true;
42 49
43 50 /**
44 - * The generator of the object.
51 + * If the User is indexable.
45 52 *
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
53 + * @context http://joinmastodon.org/ns#indexable
48 54 *
49 - * @var array
55 + * @var boolean
50 56 */
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 - );
57 + protected $indexable;
60 58
61 59 /**
62 - * Constructor.
60 + * The WebFinger Resource.
63 61 *
64 - * @param int $user_id Optional. The WordPress user ID. Default null.
62 + * @var string<url>
65 63 */
66 - public function __construct( $user_id = null ) {
67 - if ( $user_id ) {
68 - $this->_id = $user_id;
64 + protected $webfinger;
69 65
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 66 public function get_type() {
85 67 return 'Person';
86 68 }
87 69
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 70 public static function from_wp_user( $user_id ) {
96 - if ( ! user_can_activitypub( $user_id ) ) {
97 - return new \WP_Error(
71 + if ( is_user_disabled( $user_id ) ) {
72 + return new WP_Error(
98 73 'activitypub_user_not_found',
99 74 \__( 'User not found', 'activitypub' ),
100 75 array( 'status' => 404 )
101 76 );
@@ -100,103 +75,69 @@
100 75 array( 'status' => 404 )
101 76 );
102 77 }
103 78
104 - return new static( $user_id );
79 + $object = new static();
80 + $object->_id = $user_id;
81 +
82 + return $object;
105 83 }
106 84
107 85 /**
108 - * Get the user ID.
86 + * Get the User-ID.
109 87 *
110 - * @return string The user ID.
88 + * @return string The User-ID.
111 89 */
112 90 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( '/' ) );
91 + return $this->get_url();
126 92 }
127 93
128 94 /**
129 - * Get the Username.
95 + * Get the User-Name.
130 96 *
131 - * @return string The Username.
97 + * @return string The User-Name.
132 98 */
133 99 public function get_name() {
134 - return \get_the_author_meta( 'display_name', $this->_id );
100 + return \esc_attr( \get_the_author_meta( 'display_name', $this->_id ) );
135 101 }
136 102
137 103 /**
138 - * Get the User description.
104 + * Get the User-Description.
139 105 *
140 - * @return string The User description.
106 + * @return string The User-Description.
141 107 */
142 108 public function get_summary() {
143 - $description = \get_user_option( 'activitypub_description', $this->_id );
109 + $description = get_user_option( 'activitypub_description', $this->_id );
144 110 if ( empty( $description ) ) {
145 - $description = \get_user_meta( $this->_id, 'description', true );
111 + $description = get_user_meta( $this->_id, 'description', true );
146 112 }
147 113 return \wpautop( \wp_kses( $description, 'default' ) );
148 114 }
149 115
150 116 /**
151 - * Get the User url.
117 + * Get the User-Url.
152 118 *
153 - * @return string The User url.
119 + * @return string The User-Url.
154 120 */
155 121 public function get_url() {
156 - return \esc_url_raw( \get_author_posts_url( $this->_id ) );
122 + return \esc_url( \get_author_posts_url( $this->_id ) );
157 123 }
158 124
159 125 /**
160 - * Returns the User URL with @-Prefix for the username.
126 + * Returns the User-URL with @-Prefix for the username.
161 127 *
162 - * @return string The User URL with @-Prefix for the username.
128 + * @return string The User-URL with @-Prefix for the username.
163 129 */
164 130 public function get_alternate_url() {
165 - return \esc_url_raw( \trailingslashit( \get_home_url() ) . '@' . $this->get_preferred_username() );
131 + return \esc_url( \trailingslashit( get_home_url() ) . '@' . $this->get_preferred_username() );
166 132 }
167 133
168 - /**
169 - * Get the preferred username.
170 - *
171 - * @return string The preferred username.
172 - */
173 134 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;
135 + return \esc_attr( \get_the_author_meta( 'login', $this->_id ) );
182 136 }
183 137
184 - /**
185 - * Get the User icon.
186 - *
187 - * @return string[] The User icon.
188 - */
189 138 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(
139 + $icon = \esc_url(
199 140 \get_avatar_url(
200 141 $this->_id,
201 142 array( 'size' => 120 )
202 143 )
@@ -207,29 +148,24 @@
207 148 'url' => $icon,
208 149 );
209 150 }
210 151
211 - /**
212 - * Returns the header image.
213 - *
214 - * @return string[]|null The header image.
215 - */
216 152 public function get_image() {
217 - $header_image = \get_user_option( 'activitypub_header_image', $this->_id );
153 + $header_image = get_user_option( 'activitypub_header_image', $this->_id );
218 154 $image_url = null;
219 155
220 - if ( ! $header_image && \has_header_image() ) {
221 - $image_url = \get_header_image();
156 + if ( $header_image ) {
157 + $image_url = \wp_get_attachment_url( $header_image );
222 158 }
223 159
224 - if ( $header_image ) {
225 - $image_url = \wp_get_attachment_url( $header_image );
160 + if ( ! $image_url && \has_header_image() ) {
161 + $image_url = \get_header_image();
226 162 }
227 163
228 164 if ( $image_url ) {
229 165 return array(
230 166 'type' => 'Image',
231 - 'url' => \esc_url_raw( $image_url ),
167 + 'url' => esc_url( $image_url ),
232 168 );
233 169 }
234 170
235 171 return null;
@@ -234,27 +170,17 @@
234 170
235 171 return null;
236 172 }
237 173
238 - /**
239 - * Returns the date the user was created.
240 - *
241 - * @return false|string The date the user was created.
242 - */
243 174 public function get_published() {
244 - return \gmdate( ACTIVITYPUB_DATE_TIME_RFC3339, \strtotime( \get_the_author_meta( 'registered', $this->_id ) ) );
175 + return \gmdate( 'Y-m-d\TH:i:s\Z', \strtotime( \get_the_author_meta( 'registered', $this->_id ) ) );
245 176 }
246 177
247 - /**
248 - * Returns the public key.
249 - *
250 - * @return string[] The public key.
251 - */
252 178 public function get_public_key() {
253 179 return array(
254 - 'id' => $this->get_id() . '#main-key',
255 - 'owner' => $this->get_id(),
256 - 'publicKeyPem' => Actors::get_public_key( $this->get__id() ),
180 + 'id' => $this->get_id() . '#main-key',
181 + 'owner' => $this->get_id(),
182 + 'publicKeyPem' => Signature::get_public_key_for( $this->get__id() ),
257 183 );
258 184 }
259 185
260 186 /**
@@ -262,9 +188,9 @@
262 188 *
263 189 * @return string The Inbox-Endpoint.
264 190 */
265 191 public function get_inbox() {
266 - return get_rest_url_by_path( \sprintf( 'actors/%d/inbox', $this->get__id() ) );
192 + return get_rest_url_by_path( sprintf( 'actors/%d/inbox', $this->get__id() ) );
267 193 }
268 194
269 195 /**
270 196 * Returns the Outbox-API-Endpoint.
@@ -271,9 +197,9 @@
271 197 *
272 198 * @return string The Outbox-Endpoint.
273 199 */
274 200 public function get_outbox() {
275 - return get_rest_url_by_path( \sprintf( 'actors/%d/outbox', $this->get__id() ) );
201 + return get_rest_url_by_path( sprintf( 'actors/%d/outbox', $this->get__id() ) );
276 202 }
277 203
278 204 /**
279 205 * Returns the Followers-API-Endpoint.
@@ -280,9 +206,9 @@
280 206 *
281 207 * @return string The Followers-Endpoint.
282 208 */
283 209 public function get_followers() {
284 - return get_rest_url_by_path( \sprintf( 'actors/%d/followers', $this->get__id() ) );
210 + return get_rest_url_by_path( sprintf( 'actors/%d/followers', $this->get__id() ) );
285 211 }
286 212
287 213 /**
288 214 * Returns the Following-API-Endpoint.
@@ -289,61 +215,27 @@
289 215 *
290 216 * @return string The Following-Endpoint.
291 217 */
292 218 public function get_following() {
293 - return get_rest_url_by_path( \sprintf( 'actors/%d/following', $this->get__id() ) );
219 + return get_rest_url_by_path( sprintf( 'actors/%d/following', $this->get__id() ) );
294 220 }
295 221
296 222 /**
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 223 * Returns the Featured-API-Endpoint.
309 224 *
310 225 * @return string The Featured-Endpoint.
311 226 */
312 227 public function get_featured() {
313 - return get_rest_url_by_path( \sprintf( 'actors/%d/collections/featured', $this->get__id() ) );
228 + return get_rest_url_by_path( sprintf( 'actors/%d/collections/featured', $this->get__id() ) );
314 229 }
315 230
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 231 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 - );
232 + $endpoints = null;
339 233
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' ) );
234 + if ( ACTIVITYPUB_SHARED_INBOX_FEATURE ) {
235 + $endpoints = array(
236 + 'sharedInbox' => get_rest_url_by_path( 'inbox' ),
237 + );
346 238 }
347 239
348 240 return $endpoints;
349 241 }
@@ -366,177 +258,24 @@
366 258 public function get_webfinger() {
367 259 return $this->get_preferred_username() . '@' . \wp_parse_url( \home_url(), \PHP_URL_HOST );
368 260 }
369 261
370 - /**
371 - * Returns the canonical URL.
372 - *
373 - * @return string The canonical URL.
374 - */
375 262 public function get_canonical_url() {
376 263 return $this->get_url();
377 264 }
378 265
379 - /**
380 - * Returns the streams.
381 - *
382 - * @return null The streams.
383 - */
384 266 public function get_streams() {
385 267 return null;
386 268 }
387 269
388 - /**
389 - * Returns the tag.
390 - *
391 - * @return array The tag.
392 - */
393 270 public function get_tag() {
394 271 return array();
395 272 }
396 273
397 - /**
398 - * Returns the indexable state.
399 - *
400 - * @return bool Whether the user is indexable.
401 - */
402 274 public function get_indexable() {
403 275 if ( is_blog_public() ) {
404 276 return true;
405 277 } else {
406 278 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 279 }
541 280 }
542 281 }