false ) ); \remove_filter( 'http_request_args', $no_redirects, 10 ); if ( ! $object || \is_wp_error( $object ) || ! \is_array( $object ) ) { return; } if ( ! is_activity( $object ) ) { return; } /* * Only an actor's own server may vouch for an activity attributed to it, so the * host it was fetched from must equal its actor's host — the same key-host == * actor-host binding verify_key_id() enforces for signed requests, generalised * to every relayed activity type. */ if ( ! is_same_host( $object_url, $object['actor'] ?? '' ) ) { return; } /* * The requested URL is not always the host that answered: Proxy::get() re-fetches a * document from the id it declares when the two disagree, and returns the re-fetched copy. * Bind the actor to that id as well, which an authentic activity shares a host with. * * Only when the document declares one. The id is derived exactly as Proxy::get() * derives it, so the two cannot disagree about what counts as declared: whatever it treats * as id-less it returns as served, without re-fetching, and the origin check above is * already authoritative for those. Binding them here would drop relayed activities that * legitimately omit an id. */ $declared_id = isset( $object['id'] ) && \is_string( $object['id'] ) ? $object['id'] : ''; if ( '' !== $declared_id && ! is_same_host( $declared_id, $object['actor'] ?? '' ) ) { return; } // The inbox checked its blocks against the Announce, not against the activity it carries. $inner = Activity::init_from_array( $object ); if ( Moderation::activity_is_blocked( $inner ) ) { return; } $user_ids = \array_values( \array_filter( (array) $user_ids, static function ( $user_id ) use ( $inner ) { return ! Moderation::activity_is_blocked_for_user( $inner, $user_id ); } ) ); if ( ! $user_ids ) { return; } $type = \strtolower( $object['type'] ); /** * Fires after an Announce has been received. * * @param array $object The object. * @param int[] $user_ids The ids of the local blog-users. * @param string $type The type of the activity. * @param \Activitypub\Activity\Activity|null $activity The activity object. */ \do_action( 'activitypub_inbox', $object, (array) $user_ids, $type, $activity ); /** * Fires after an Announce of a specific type has been received. * * @param array $object The object. * @param int[] $user_ids The ids of the local blog-users. * @param \Activitypub\Activity\Activity|null $activity The activity object. */ \do_action( "activitypub_inbox_{$type}", $object, (array) $user_ids, $activity ); } /** * Try to save the Announce. * * @param array $activity The activity-object. * @param int|int[] $user_ids The id of the local blog-user. * * @return void */ public static function maybe_save_announce( $activity, $user_ids ) { $url = object_to_uri( $activity ); if ( empty( $url ) ) { return; } // Match any status, so a repost that was marked as spam or trashed still counts as seen. $exists = Comment::object_id_to_comment( \esc_url_raw( $url ), array( 'status' => 'any' ) ); if ( $exists ) { return; } // If the object is a Create activity, extract the actual object from it. if ( isset( $activity['object']['type'] ) && 'Create' === $activity['object']['type'] ) { $activity['object'] = object_to_uri( $activity['object']['object'] ); } $success = false; $result = Interactions::add_reaction( $activity ); if ( $result && ! \is_wp_error( $result ) ) { $success = true; $result = \get_comment( $result ); } /** * Fires after an ActivityPub Announce activity has been handled. * * @param array $activity The ActivityPub activity data. * @param int[] $user_ids The local user IDs. * @param bool $success True on success, false otherwise. * @param array|string|int|\WP_Error|false $result The WP_Comment object of the created announce/repost comment, or null if creation failed. */ \do_action( 'activitypub_handled_announce', $activity, (array) $user_ids, $success, $result ); } }