to_array(); } $recipients = extract_recipients_from_activity( $data ); if ( empty( $recipients ) ) { return false; } return ! empty( \array_intersect( $recipients, ACTIVITYPUB_PUBLIC_AUDIENCE_IDENTIFIERS ) ); } /** * Check if passed Activity is a reply. * * @param array $data The Activity object as array. * * @return boolean True if a reply, false if not. */ function is_activity_reply( $data ) { return ! empty( $data['object']['inReplyTo'] ); } /** * Check if passed Activity is a quote. * * Checks for quote properties: quote, quoteUrl, quoteUri, or _misskey_quote. * * @param array $data The Activity object as array. * * @return boolean True if a quote, false if not. */ function is_quote_activity( $data ) { return ! empty( $data['object']['quote'] ) || ! empty( $data['object']['quoteUrl'] ) || ! empty( $data['object']['quoteUri'] ) || ! empty( $data['object']['_misskey_quote'] ); } /** * Get the URI of an ActivityPub object. * * @param array|string $data The ActivityPub object. * * @return string|null The URI of the ActivityPub object. */ function object_to_uri( $data ) { // Check whether it is already simple. if ( ! $data || \is_string( $data ) ) { return $data; } if ( \is_object( $data ) ) { $data = $data->to_array(); } /* * Check if it is a list, then take first item. * This plugin does not support collections. */ if ( \array_is_list( $data ) ) { $data = $data[0]; } // Check if it is simplified now. if ( \is_string( $data ) ) { return $data; } $type = 'Object'; if ( isset( $data['type'] ) ) { $type = $data['type']; } // Return part of Object that makes most sense. switch ( $type ) { case 'Audio': // See https://www.w3.org/TR/activitystreams-vocabulary/#dfn-audio. case 'Document': // See https://www.w3.org/TR/activitystreams-vocabulary/#dfn-document. case 'Image': // See https://www.w3.org/TR/activitystreams-vocabulary/#dfn-image. case 'Video': // See https://www.w3.org/TR/activitystreams-vocabulary/#dfn-video. $data = object_to_uri( $data['url'] ); break; case 'Link': // See https://www.w3.org/TR/activitystreams-vocabulary/#dfn-link. case 'Mention': // See https://www.w3.org/TR/activitystreams-vocabulary/#dfn-mention. $data = $data['href']; break; case 'FeaturedItem': // See https://github.com/mastodon/featured_collections/pull/1. $data = object_to_uri( $data['featuredObject'] ?? null ); break; default: if ( isset( $data['id'] ) ) { $data = $data['id']; } elseif ( isset( $data['url'] ) ) { $data = object_to_uri( $data['url'] ); } elseif ( isset( $data['href'] ) ) { $data = $data['href']; } else { $data = null; } break; } return $data; } /** * Check whether two references point at the same actor. * * Both values are resolved to their canonical URI via object_to_uri() before * comparison. Empty references never match, so a missing actor can never be * mistaken for a match. * * @param array|object|string $a The first actor reference. * @param array|object|string $b The second actor reference. * * @return bool True when both resolve to the same non-empty URI. */ function is_same_actor( $a, $b ) { $a = object_to_uri( $a ); $b = object_to_uri( $b ); return ! empty( $a ) && ! empty( $b ) && $a === $b; } /** * Check whether two references live on the same host. * * Both values are resolved to their canonical URI via object_to_uri(), then * their hosts are compared case-insensitively. Empty references, or references * without a host, never match. * * @param array|object|string $a The first reference. * @param array|object|string $b The second reference. * * @return bool True when both resolve to a URI on the same host. */ function is_same_host( $a, $b ) { $host_a = \wp_parse_url( (string) object_to_uri( $a ), PHP_URL_HOST ); $host_b = \wp_parse_url( (string) object_to_uri( $b ), PHP_URL_HOST ); return ! empty( $host_a ) && ! empty( $host_b ) && \strtolower( $host_a ) === \strtolower( $host_b ); } /** * Whether an object is served under its own canonical id. * * An object is only trustworthy to cache when its own `id` is the URL it was * actually served from: otherwise one host could serve a document — and its * public key — under another host's id. Reads the raw `id` attribute only * (never the `url`/`href` fallback that object_to_uri() applies), because the * cache is keyed on `id`, so "is this canonical?" must ask the same field the * write uses. The comparison ignores the URL fragment and a trailing slash; * everything else (scheme, host, port, path, query) must match exactly. * Host-level equality is deliberately NOT enough — any different id on the same * host is still a distinct cache entry that a document served elsewhere must not write. * * @param array|string $item The fetched object, or its id. * @param string $url The URL the object was served from. * * @return bool True when the object's id is the canonical URL it was served from. */ function id_matches_url( $item, $url ) { if ( \is_array( $item ) ) { $id = isset( $item['id'] ) && \is_string( $item['id'] ) ? $item['id'] : ''; } elseif ( \is_string( $item ) ) { $id = $item; } else { $id = ''; } $id = \strip_fragment_from_url( $id ); $url = \strip_fragment_from_url( (string) $url ); if ( '' === $id || '' === $url ) { return false; } return \untrailingslashit( $id ) === \untrailingslashit( $url ); } /** * Check if an `$data` is an Activity. * * @see https://www.w3.org/ns/activitystreams#activities * * @param array|object|string $data The data to check. * * @return boolean True if the `$data` is an Activity, false otherwise. */ function is_activity( $data ) { /** * Filters the activity types. * * @param array $types The activity types. */ $types = \apply_filters( 'activitypub_activity_types', Activity::TYPES ); return _is_type_of( $data, $types ); } /** * Check if an `$data` is an Activity Object. * * @see https://www.w3.org/TR/activitystreams-vocabulary/#object-types * * @param array|object|string $data The data to check. * * @return boolean True if the `$data` is an Activity Object, false otherwise. */ function is_activity_object( $data ) { /** * Filters the activity object types. * * @param array $types The activity object types. */ $types = \apply_filters( 'activitypub_activity_object_types', Base_Object::TYPES ); return _is_type_of( $data, $types ); } /** * Check if an `$data` is an Actor. * * @see https://www.w3.org/ns/activitystreams#actor * * @param array|object|string $data The data to check. * * @return boolean True if the `$data` is an Actor, false otherwise. */ function is_actor( $data ) { /** * Filters the actor types. * * @param array $types The actor types. */ $types = \apply_filters( 'activitypub_actor_types', Actor::TYPES ); return _is_type_of( $data, $types ); } /** * Check if an `$data` is a Collection. * * @see https://www.w3.org/ns/activitystreams#collections * * @param array|object|string $data The data to check. * * @return boolean True if the `$data` is a Collection, false otherwise. */ function is_collection( $data ) { /** * Filters the collection types. * * @param array $types The collection types. */ $types = \apply_filters( 'activitypub_collection_types', array( 'Collection', 'OrderedCollection', 'CollectionPage', 'OrderedCollectionPage' ) ); return _is_type_of( $data, $types ); } /** * Private helper to check if $data is of a given type set. * * @param array|object|string $data The data to check. * @param array $types The types to check against. * * @return boolean True if $data is of one of the types, false otherwise. */ function _is_type_of( $data, $types ) { if ( \is_string( $data ) ) { return \in_array( $data, $types, true ); } if ( \is_array( $data ) && isset( $data['type'] ) ) { return \in_array( $data['type'], $types, true ); } if ( $data instanceof Base_Object ) { return \in_array( $data->get_type(), $types, true ); } return false; }