PluginProbe
ActivityPub / 7.8.2
ActivityPub v7.8.2
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/class-query.php +18 -119 9.3.17.8.2 View file →
@@ -6,13 +6,11 @@
6 6 */
7 7
8 8 namespace Activitypub;
9 9
10 -use Activitypub\Activity\Extended_Object\Feature_Authorization;
11 10 use Activitypub\Activity\Extended_Object\Quote_Authorization;
12 11 use Activitypub\Collection\Actors;
13 12 use Activitypub\Collection\Outbox;
14 -use Activitypub\Handler\Feature_Request;
15 13 use Activitypub\Transformer\Factory;
16 14
17 15 /**
18 16 * Singleton class to handle and store the ActivityPub query.
@@ -140,17 +138,10 @@
140 138 */
141 139 private function prepare_activitypub_data() {
142 140 $queried_object = $this->get_queried_object();
143 141
144 - if ( \get_query_var( 'stamp' ) ) {
145 - if ( $queried_object instanceof \WP_Post ) {
146 - return $this->maybe_get_stamp();
147 - }
148 -
149 - // Note: the blog actor's `actor` query var is '0', which is falsy but valid.
150 - if ( $queried_object instanceof \WP_User || '' !== \get_query_var( 'actor' ) ) {
151 - return $this->maybe_get_actor_stamp();
152 - }
142 + if ( $queried_object instanceof \WP_Post && \get_query_var( 'stamp' ) ) {
143 + return $this->maybe_get_stamp();
153 144 }
154 145
155 146 // Check for Outbox Activity.
156 147 if (
@@ -206,17 +197,10 @@
206 197 $queried_object = \get_post( $post_id );
207 198 }
208 199 }
209 200
210 - /*
211 - * Check Term by ID, unless the request names an author. Other plugins set `term_id` on
212 - * requests that are not about a term at all, Polylang puts its language term on every
213 - * request, and a term here would answer `?author=0` with an OrderedCollection where
214 - * Mastodon expected the blog actor. Leaving the object unset lets the author resolution
215 - * below, and the blog-actor handling in get_activitypub_object_id(), run as usual. An
216 - * absent `author` reads as an empty string, and `?author=0` as the string "0".
217 - */
218 - if ( ! $queried_object && '' === \get_query_var( 'author', '' ) ) {
201 + // Check Term by ID.
202 + if ( ! $queried_object ) {
219 203 $term_id = \get_query_var( 'term_id' );
220 204 if ( $term_id ) {
221 205 $queried_object = \get_term( $term_id );
222 206 }
@@ -235,9 +219,9 @@
235 219 * Filters the queried object.
236 220 *
237 221 * @param \WP_Term|\WP_Post_Type|\WP_Post|\WP_User|\WP_Comment|null $queried_object The queried object.
238 222 */
239 - return \apply_filters( 'activitypub_queried_object', $queried_object );
223 + return apply_filters( 'activitypub_queried_object', $queried_object );
240 224 }
241 225
242 226 /**
243 227 * Get the virtual object.
@@ -242,11 +226,12 @@
242 226 /**
243 227 * Get the virtual object.
244 228 *
245 229 * Virtual objects are objects that are not stored in the database, but are created on the fly.
246 - * The plugin currently supports one virtual object: The Blog-Actor.
230 + * The plugins currently supports two virtual objects: The Blog-Actor and the Application-Actor.
247 231 *
248 232 * @see \Activitypub\Model\Blog
233 + * @see \Activitypub\Model\Application
249 234 *
250 235 * @return object|null The virtual object.
251 236 */
252 237 protected function maybe_get_virtual_object() {
@@ -257,9 +242,9 @@
257 242 }
258 243
259 244 $author_id = url_to_authorid( $url );
260 245
261 - if ( ! \is_numeric( $author_id ) ) {
246 + if ( ! is_numeric( $author_id ) ) {
262 247 $author_id = $url;
263 248 }
264 249
265 250 $user = Actors::get_by_various( $author_id );
@@ -306,25 +291,19 @@
306 291 $this->is_activitypub_request = true;
307 292
308 293 // The other (more common) option to make an ActivityPub request is to send an Accept header.
309 294 } elseif ( isset( $_SERVER['HTTP_ACCEPT'] ) ) {
295 + $accept = \sanitize_text_field( \wp_unslash( $_SERVER['HTTP_ACCEPT'] ) );
296 +
310 297 /*
311 - * The Accept-header decision is delegated to accept_prefers_activitypub() so the plugin and the
312 - * Surge cache drop-in classify byte-for-byte identically. Both must hand it the same raw
313 - * header, and they reach that raw form differently on purpose: this runs after
314 - * wp_magic_quotes() has addslashed $_SERVER, so it wp_unslash()es to recover the original
315 - * bytes; the drop-in runs before wp_magic_quotes() and passes its already-raw value
316 - * untouched. Do NOT sanitize it (the drop-in can't, its sanitizers aren't loaded yet) and
317 - * the helper must not stripslashes() either (that would corrupt the drop-in's genuine
318 - * bytes). It is only used to pick a content type, never stored or echoed.
319 - *
320 - * The request is ActivityPub when the highest-priority (by `q`, then order) media type is
321 - * an ActivityPub type (`application/activity+json`, or `application/ld+json` with the AS2
322 - * profile). A browser (`text/html` at q=1) gets the normal page; a client that prefers
323 - * ActivityPub but also accepts HTML as a low-`q` fallback (Mastodon) gets ActivityPub.
298 + * $accept can be a single value, or a comma separated list of values.
299 + * We want to support both scenarios,
300 + * and return true when the header includes at least one of the following:
301 + * - application/activity+json
302 + * - application/ld+json
303 + * - application/json
324 304 */
325 - // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Classified only; wp_unslash() recovers the raw bytes the pre-plugin cache path sees, and it must not be sanitized.
326 - if ( accept_prefers_activitypub( \wp_unslash( $_SERVER['HTTP_ACCEPT'] ) ) ) {
305 + if ( \preg_match( '/(application\/(ld\+json|activity\+json|json))/i', $accept ) ) {
327 306 \defined( 'ACTIVITYPUB_REQUEST' ) || \define( 'ACTIVITYPUB_REQUEST', true );
328 307 $this->is_activitypub_request = true;
329 308 }
330 309 }
@@ -358,12 +337,8 @@
358 337 if ( \get_option( 'activitypub_content_negotiation', '1' ) ) {
359 338 $return = true;
360 339 }
361 340
362 - if ( \is_author() && \get_user_option( 'activitypub_use_permalink_as_id', \get_queried_object_id() ) ) {
363 - $return = true;
364 - }
365 -
366 341 /**
367 342 * Filters whether content negotiation should be forced.
368 343 *
369 344 * @param bool $return Whether content negotiation should be forced.
@@ -421,20 +396,9 @@
421 396 if ( ! $meta ) {
422 397 return false;
423 398 }
424 399
425 - $post = $this->get_queried_object();
426 -
427 - /*
428 - * Only quote-authorization meta may be reflected as a stamp, and only for the queried
429 - * post. Checking the post id alone would still let an unauthenticated request read any
430 - * of that post's meta rows (e.g. _edit_lock or private custom fields) by guessing a
431 - * meta_id, so the meta key is verified too.
432 - */
433 - if ( '_activitypub_quoted_by' !== $meta->meta_key || (int) $meta->post_id !== $post->ID ) {
434 - return false;
435 - }
436 -
400 + $post = $this->get_queried_object();
437 401 $user_uri = get_user_id( $post->post_author );
438 402
439 403 if ( ! $user_uri ) {
440 404 return false;
@@ -455,73 +419,8 @@
455 419 $activitypub_object->set_interaction_target( get_post_id( $post->ID ) );
456 420
457 421 $this->activitypub_object = $activitypub_object;
458 422 $this->activitypub_object_id = $activitypub_object->get_id();
459 -
460 - return true;
461 - }
462 -
463 - /**
464 - * Maybe get a FeatureAuthorization object from an actor-scoped stamp.
465 - *
466 - * Resolves URLs of the form `?actor=USER_ID&stamp=STAMP_ID` against the
467 - * actor's stamp store, see {@see Feature_Request::get_stamp()}. Ownership
468 - * is enforced by resolving the stamp scoped to the queried actor, which
469 - * includes the blog actor (`actor=0`).
470 - *
471 - * @return bool True if a FeatureAuthorization was prepared, false otherwise.
472 - */
473 - private function maybe_get_actor_stamp() {
474 - $stamp_id = (int) \get_query_var( 'stamp' );
475 - $actor_var = \get_query_var( 'actor' );
476 -
477 - if ( ! $stamp_id ) {
478 - return false;
479 - }
480 -
481 - if ( '' === $actor_var ) {
482 - $queried = $this->get_queried_object();
483 - if ( ! $queried instanceof \WP_User ) {
484 - return false;
485 - }
486 -
487 - $actor_id = (int) $queried->ID;
488 - } else {
489 - // Values like '0e1' or '1.5' pass is_numeric() but cast to 0/1 and alias
490 - // an actor, so require a plain decimal integer before casting.
491 - if ( ! \ctype_digit( (string) $actor_var ) ) {
492 - return false;
493 - }
494 -
495 - $actor_id = (int) $actor_var;
496 - }
497 -
498 - $instrument = Feature_Request::get_stamp( $actor_id, $stamp_id );
499 - if ( null === $instrument ) {
500 - return false;
501 - }
502 -
503 - $actor = Actors::get_by_id( $actor_id );
504 - if ( \is_wp_error( $actor ) ) {
505 - return false;
506 - }
507 -
508 - $stamp_url = \add_query_arg(
509 - array(
510 - 'actor' => $actor_id,
511 - 'stamp' => $stamp_id,
512 - ),
513 - \home_url( '/' )
514 - );
515 -
516 - $authorization = new Feature_Authorization();
517 - $authorization->set_id( $stamp_url );
518 - $authorization->set_attributed_to( $actor->get_id() );
519 - $authorization->set_interacting_object( $instrument );
520 - $authorization->set_interaction_target( $actor->get_id() );
521 -
522 - $this->activitypub_object = $authorization;
523 - $this->activitypub_object_id = $authorization->get_id();
524 423
525 424 return true;
526 425 }
527 426 }