PluginProbe
ActivityPub / 5.7.0
ActivityPub v5.7.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/class-query.php +38 -206 9.2.05.7.0 View file →
@@ -6,13 +6,10 @@
6 6 */
7 7
8 8 namespace Activitypub;
9 9
10 -use Activitypub\Activity\Extended_Object\Feature_Authorization;
11 -use Activitypub\Activity\Extended_Object\Quote_Authorization;
12 10 use Activitypub\Collection\Actors;
13 11 use Activitypub\Collection\Outbox;
14 -use Activitypub\Handler\Feature_Request;
15 12 use Activitypub\Transformer\Factory;
16 13
17 14 /**
18 15 * Singleton class to handle and store the ActivityPub query.
@@ -140,19 +137,8 @@
140 137 */
141 138 private function prepare_activitypub_data() {
142 139 $queried_object = $this->get_queried_object();
143 140
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 - }
153 - }
154 -
155 141 // Check for Outbox Activity.
156 142 if (
157 143 $queried_object instanceof \WP_Post &&
158 144 Outbox::POST_TYPE === $queried_object->post_type
@@ -206,16 +192,8 @@
206 192 $queried_object = \get_post( $post_id );
207 193 }
208 194 }
209 195
210 - // Check Term by ID.
211 - if ( ! $queried_object ) {
212 - $term_id = \get_query_var( 'term_id' );
213 - if ( $term_id ) {
214 - $queried_object = \get_term( $term_id );
215 - }
216 - }
217 -
218 196 // Try to get Author by ID.
219 197 if ( ! $queried_object ) {
220 198 $url = $this->get_request_url();
221 199 $author_id = url_to_authorid( $url );
@@ -228,9 +206,9 @@
228 206 * Filters the queried object.
229 207 *
230 208 * @param \WP_Term|\WP_Post_Type|\WP_Post|\WP_User|\WP_Comment|null $queried_object The queried object.
231 209 */
232 - return \apply_filters( 'activitypub_queried_object', $queried_object );
210 + return apply_filters( 'activitypub_queried_object', $queried_object );
233 211 }
234 212
235 213 /**
236 214 * Get the virtual object.
@@ -235,11 +213,12 @@
235 213 /**
236 214 * Get the virtual object.
237 215 *
238 216 * Virtual objects are objects that are not stored in the database, but are created on the fly.
239 - * The plugin currently supports one virtual object: The Blog-Actor.
217 + * The plugins currently supports two virtual objects: The Blog-Actor and the Application-Actor.
240 218 *
241 219 * @see \Activitypub\Model\Blog
220 + * @see \Activitypub\Model\Application
242 221 *
243 222 * @return object|null The virtual object.
244 223 */
245 224 protected function maybe_get_virtual_object() {
@@ -250,9 +229,9 @@
250 229 }
251 230
252 231 $author_id = url_to_authorid( $url );
253 232
254 - if ( ! \is_numeric( $author_id ) ) {
233 + if ( ! is_numeric( $author_id ) ) {
255 234 $author_id = $url;
256 235 }
257 236
258 237 $user = Actors::get_by_various( $author_id );
@@ -268,9 +247,9 @@
268 247 * Get the request URL.
269 248 *
270 249 * @return string|null The request URL.
271 250 */
272 - public function get_request_url() {
251 + protected function get_request_url() {
273 252 if ( ! isset( $_SERVER['REQUEST_URI'] ) ) {
274 253 return null;
275 254 }
276 255
@@ -287,81 +266,52 @@
287 266 *
288 267 * @return bool True if the request is an ActivityPub request, false otherwise.
289 268 */
290 269 public function is_activitypub_request() {
291 - if ( ! isset( $this->is_activitypub_request ) ) {
292 - global $wp_query;
270 + if ( isset( $this->is_activitypub_request ) ) {
271 + return $this->is_activitypub_request;
272 + }
293 273
294 - $this->is_activitypub_request = false;
274 + global $wp_query;
295 275
296 - // One can trigger an ActivityPub request by adding `?activitypub` to the URL.
297 - if ( isset( $wp_query->query_vars['activitypub'] ) || isset( $_GET['activitypub'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
298 - \defined( 'ACTIVITYPUB_REQUEST' ) || \define( 'ACTIVITYPUB_REQUEST', true );
299 - $this->is_activitypub_request = true;
276 + // One can trigger an ActivityPub request by adding `?activitypub` to the URL.
277 + if (
278 + isset( $wp_query->query_vars['activitypub'] ) ||
279 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended
280 + isset( $_GET['activitypub'] )
281 + ) {
282 + \defined( 'ACTIVITYPUB_REQUEST' ) || \define( 'ACTIVITYPUB_REQUEST', true );
283 + $this->is_activitypub_request = true;
300 284
301 - // The other (more common) option to make an ActivityPub request is to send an Accept header.
302 - } elseif ( isset( $_SERVER['HTTP_ACCEPT'] ) ) {
303 - /*
304 - * The Accept-header decision is delegated to is_json_only_accept() so the plugin and the
305 - * Surge cache drop-in classify byte-for-byte identically. Both must hand it the same raw
306 - * header, and they reach that raw form differently on purpose: this runs after
307 - * wp_magic_quotes() has addslashed $_SERVER, so it wp_unslash()es to recover the original
308 - * bytes; the drop-in runs before wp_magic_quotes() and passes its already-raw value
309 - * untouched. Do NOT sanitize it (the drop-in can't, its sanitizers aren't loaded yet) and
310 - * the helper must not stripslashes() either (that would corrupt the drop-in's genuine
311 - * bytes). It is only used to pick a content type, never stored or echoed.
312 - *
313 - * The request is ActivityPub only when *every* media type is JSON; a client that also
314 - * accepts HTML (e.g. a browser sending `text/html, application/activity+json`) gets the
315 - * normal HTML page.
316 - */
317 - // 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.
318 - if ( is_json_only_accept( \wp_unslash( $_SERVER['HTTP_ACCEPT'] ) ) ) {
319 - \defined( 'ACTIVITYPUB_REQUEST' ) || \define( 'ACTIVITYPUB_REQUEST', true );
320 - $this->is_activitypub_request = true;
321 - }
322 - }
285 + return true;
323 286 }
324 287
325 - /**
326 - * Filters whether the current request is an ActivityPub request.
327 - *
328 - * @param bool $is_activitypub_request True if the request is an ActivityPub request, false otherwise.
288 + /*
289 + * The other (more common) option to make an ActivityPub request
290 + * is to send an Accept header.
329 291 */
330 - return \apply_filters( 'activitypub_is_activitypub_request', $this->is_activitypub_request );
331 - }
292 + if ( isset( $_SERVER['HTTP_ACCEPT'] ) ) {
293 + $accept = \sanitize_text_field( \wp_unslash( $_SERVER['HTTP_ACCEPT'] ) );
332 294
333 - /**
334 - * Check if content negotiation is allowed for a request.
335 - *
336 - * @return bool True if content negotiation is allowed, false otherwise.
337 - */
338 - public function should_negotiate_content() {
339 - $return = false;
340 - $always_negotiate = array( 'p', 'c', 'author', 'actor', 'stamp', 'preview', 'activitypub' );
341 - $url = \wp_parse_url( $this->get_request_url(), PHP_URL_QUERY );
342 - $query = array();
343 - \wp_parse_str( $url, $query );
295 + /*
296 + * $accept can be a single value, or a comma separated list of values.
297 + * We want to support both scenarios,
298 + * and return true when the header includes at least one of the following:
299 + * - application/activity+json
300 + * - application/ld+json
301 + * - application/json
302 + */
303 + if ( \preg_match( '/(application\/(ld\+json|activity\+json|json))/i', $accept ) ) {
304 + \defined( 'ACTIVITYPUB_REQUEST' ) || \define( 'ACTIVITYPUB_REQUEST', true );
305 + $this->is_activitypub_request = true;
344 306
345 - // Check if any of the query params are in the `$always_negotiate` array.
346 - if ( \array_intersect( \array_keys( $query ), $always_negotiate ) ) {
347 - $return = true;
307 + return true;
308 + }
348 309 }
349 310
350 - if ( \get_option( 'activitypub_content_negotiation', '1' ) ) {
351 - $return = true;
352 - }
311 + $this->is_activitypub_request = false;
353 312
354 - if ( \is_author() && \get_user_option( 'activitypub_use_permalink_as_id', \get_queried_object_id() ) ) {
355 - $return = true;
356 - }
357 -
358 - /**
359 - * Filters whether content negotiation should be forced.
360 - *
361 - * @param bool $return Whether content negotiation should be forced.
362 - */
363 - return \apply_filters( 'activitypub_should_negotiate_content', $return );
313 + return false;
364 314 }
365 315
366 316 /**
367 317 * Check if the current request is from the old host.
@@ -396,124 +346,6 @@
396 346 * @param bool $state Optional. The state to set. Default true.
397 347 */
398 348 public function set_old_host_request( $state = true ) {
399 349 $this->is_old_host_request = $state;
400 - }
401 -
402 - /**
403 - * Maybe get a QuoteAuthorization object from a stamp.
404 - *
405 - * @return bool True if the object was prepared, false otherwise.
406 - */
407 - private function maybe_get_stamp() {
408 - require_once ABSPATH . 'wp-admin/includes/post.php';
409 -
410 - $stamp = \get_query_var( 'stamp' );
411 - $meta = \get_post_meta_by_id( (int) $stamp );
412 -
413 - if ( ! $meta ) {
414 - return false;
415 - }
416 -
417 - $post = $this->get_queried_object();
418 -
419 - /*
420 - * Only quote-authorization meta may be reflected as a stamp, and only for the queried
421 - * post. Checking the post id alone would still let an unauthenticated request read any
422 - * of that post's meta rows (e.g. _edit_lock or private custom fields) by guessing a
423 - * meta_id, so the meta key is verified too.
424 - */
425 - if ( '_activitypub_quoted_by' !== $meta->meta_key || (int) $meta->post_id !== $post->ID ) {
426 - return false;
427 - }
428 -
429 - $user_uri = get_user_id( $post->post_author );
430 -
431 - if ( ! $user_uri ) {
432 - return false;
433 - }
434 -
435 - $stamp_uri = \add_query_arg(
436 - array(
437 - 'p' => $post->ID,
438 - 'stamp' => $meta->meta_id,
439 - ),
440 - \home_url( '/' )
441 - );
442 -
443 - $activitypub_object = new Quote_Authorization();
444 - $activitypub_object->set_id( $stamp_uri );
445 - $activitypub_object->set_attributed_to( $user_uri );
446 - $activitypub_object->set_interacting_object( $meta->meta_value );
447 - $activitypub_object->set_interaction_target( get_post_id( $post->ID ) );
448 -
449 - $this->activitypub_object = $activitypub_object;
450 - $this->activitypub_object_id = $activitypub_object->get_id();
451 -
452 - return true;
453 - }
454 -
455 - /**
456 - * Maybe get a FeatureAuthorization object from an actor-scoped stamp.
457 - *
458 - * Resolves URLs of the form `?actor=USER_ID&stamp=STAMP_ID` against the
459 - * actor's stamp store, see {@see Feature_Request::get_stamp()}. Ownership
460 - * is enforced by resolving the stamp scoped to the queried actor, which
461 - * includes the blog actor (`actor=0`).
462 - *
463 - * @return bool True if a FeatureAuthorization was prepared, false otherwise.
464 - */
465 - private function maybe_get_actor_stamp() {
466 - $stamp_id = (int) \get_query_var( 'stamp' );
467 - $actor_var = \get_query_var( 'actor' );
468 -
469 - if ( ! $stamp_id ) {
470 - return false;
471 - }
472 -
473 - if ( '' === $actor_var ) {
474 - $queried = $this->get_queried_object();
475 - if ( ! $queried instanceof \WP_User ) {
476 - return false;
477 - }
478 -
479 - $actor_id = (int) $queried->ID;
480 - } else {
481 - // Values like '0e1' or '1.5' pass is_numeric() but cast to 0/1 and alias
482 - // an actor, so require a plain decimal integer before casting.
483 - if ( ! \ctype_digit( (string) $actor_var ) ) {
484 - return false;
485 - }
486 -
487 - $actor_id = (int) $actor_var;
488 - }
489 -
490 - $instrument = Feature_Request::get_stamp( $actor_id, $stamp_id );
491 - if ( null === $instrument ) {
492 - return false;
493 - }
494 -
495 - $actor = Actors::get_by_id( $actor_id );
496 - if ( \is_wp_error( $actor ) ) {
497 - return false;
498 - }
499 -
500 - $stamp_url = \add_query_arg(
501 - array(
502 - 'actor' => $actor_id,
503 - 'stamp' => $stamp_id,
504 - ),
505 - \home_url( '/' )
506 - );
507 -
508 - $authorization = new Feature_Authorization();
509 - $authorization->set_id( $stamp_url );
510 - $authorization->set_attributed_to( $actor->get_id() );
511 - $authorization->set_interacting_object( $instrument );
512 - $authorization->set_interaction_target( $actor->get_id() );
513 -
514 - $this->activitypub_object = $authorization;
515 - $this->activitypub_object_id = $authorization->get_id();
516 -
517 - return true;
518 350 }
519 351 }