PluginProbe
ActivityPub / 9.2.1
ActivityPub v9.2.1
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/functions-activity.php +102 -17 8.2.19.2.1 View file →
@@ -50,9 +50,19 @@
50 50 foreach ( array( 'to', 'bto', 'cc', 'bcc', 'audience' ) as $i ) {
51 51 $recipient_items = \array_merge( $recipient_items, extract_recipients_from_activity_property( $i, $data ) );
52 52 }
53 53
54 - return \array_unique( $recipient_items );
54 + // An Accept/Reject that wraps a Follow is addressed only through the embedded Follow's actor.
55 + if (
56 + \in_array( $data['type'], array( 'Accept', 'Reject' ), true ) &&
57 + ! empty( $data['object'] ) &&
58 + \is_array( $data['object'] ) &&
59 + ! empty( $data['object']['actor'] )
60 + ) {
61 + $recipient_items[] = object_to_uri( $data['object']['actor'] );
62 + }
63 +
64 + return \array_unique( \array_filter( $recipient_items ) );
55 65 }
56 66
57 67 /**
58 68 * Extract recipient URLs from a specific property of an Activity object.
@@ -92,21 +102,21 @@
92 102 * ACTIVITYPUB_CONTENT_VISIBILITY_PRIVATE.
93 103 */
94 104 function get_activity_visibility( $activity ) {
95 105 // Set default visibility for specific activity types.
96 - if ( ! empty( $activity['type'] ) && in_array( $activity['type'], array( 'Accept', 'Delete', 'Follow', 'Reject', 'Undo' ), true ) ) {
106 + if ( ! empty( $activity['type'] ) && \in_array( $activity['type'], array( 'Accept', 'Delete', 'Follow', 'Reject', 'Undo' ), true ) ) {
97 107 return ACTIVITYPUB_CONTENT_VISIBILITY_PRIVATE;
98 108 }
99 109
100 110 // Check 'to' field for public visibility.
101 111 $to = extract_recipients_from_activity_property( 'to', $activity );
102 - if ( ! empty( array_intersect( $to, ACTIVITYPUB_PUBLIC_AUDIENCE_IDENTIFIERS ) ) ) {
112 + if ( ! empty( \array_intersect( $to, ACTIVITYPUB_PUBLIC_AUDIENCE_IDENTIFIERS ) ) ) {
103 113 return ACTIVITYPUB_CONTENT_VISIBILITY_PUBLIC;
104 114 }
105 115
106 116 // Check 'cc' field for quiet public visibility.
107 117 $cc = extract_recipients_from_activity_property( 'cc', $activity );
108 - if ( ! empty( array_intersect( $cc, ACTIVITYPUB_PUBLIC_AUDIENCE_IDENTIFIERS ) ) ) {
118 + if ( ! empty( \array_intersect( $cc, ACTIVITYPUB_PUBLIC_AUDIENCE_IDENTIFIERS ) ) ) {
109 119 return ACTIVITYPUB_CONTENT_VISIBILITY_QUIET_PUBLIC;
110 120 }
111 121
112 122 return ACTIVITYPUB_CONTENT_VISIBILITY_PRIVATE;
@@ -132,9 +142,9 @@
132 142 if ( empty( $recipients ) ) {
133 143 return false;
134 144 }
135 145
136 - return ! empty( array_intersect( $recipients, ACTIVITYPUB_PUBLIC_AUDIENCE_IDENTIFIERS ) );
146 + return ! empty( \array_intersect( $recipients, ACTIVITYPUB_PUBLIC_AUDIENCE_IDENTIFIERS ) );
137 147 }
138 148
139 149 /**
140 150 * Check if passed Activity is a reply.
@@ -171,13 +181,13 @@
171 181 * @return string|null The URI of the ActivityPub object.
172 182 */
173 183 function object_to_uri( $data ) {
174 184 // Check whether it is already simple.
175 - if ( ! $data || is_string( $data ) ) {
185 + if ( ! $data || \is_string( $data ) ) {
176 186 return $data;
177 187 }
178 188
179 - if ( is_object( $data ) ) {
189 + if ( \is_object( $data ) ) {
180 190 $data = $data->to_array();
181 191 }
182 192
183 193 /*
@@ -183,14 +193,14 @@
183 193 /*
184 194 * Check if it is a list, then take first item.
185 195 * This plugin does not support collections.
186 196 */
187 - if ( array_is_list( $data ) ) {
197 + if ( \array_is_list( $data ) ) {
188 198 $data = $data[0];
189 199 }
190 200
191 201 // Check if it is simplified now.
192 - if ( is_string( $data ) ) {
202 + if ( \is_string( $data ) ) {
193 203 return $data;
194 204 }
195 205
196 206 $type = 'Object';
@@ -232,8 +242,83 @@
232 242 return $data;
233 243 }
234 244
235 245 /**
246 + * Check whether two references point at the same actor.
247 + *
248 + * Both values are resolved to their canonical URI via object_to_uri() before
249 + * comparison. Empty references never match, so a missing actor can never be
250 + * mistaken for a match.
251 + *
252 + * @param array|object|string $a The first actor reference.
253 + * @param array|object|string $b The second actor reference.
254 + *
255 + * @return bool True when both resolve to the same non-empty URI.
256 + */
257 +function is_same_actor( $a, $b ) {
258 + $a = object_to_uri( $a );
259 + $b = object_to_uri( $b );
260 +
261 + return ! empty( $a ) && ! empty( $b ) && $a === $b;
262 +}
263 +
264 +/**
265 + * Check whether two references live on the same host.
266 + *
267 + * Both values are resolved to their canonical URI via object_to_uri(), then
268 + * their hosts are compared case-insensitively. Empty references, or references
269 + * without a host, never match.
270 + *
271 + * @param array|object|string $a The first reference.
272 + * @param array|object|string $b The second reference.
273 + *
274 + * @return bool True when both resolve to a URI on the same host.
275 + */
276 +function is_same_host( $a, $b ) {
277 + $host_a = \wp_parse_url( (string) object_to_uri( $a ), PHP_URL_HOST );
278 + $host_b = \wp_parse_url( (string) object_to_uri( $b ), PHP_URL_HOST );
279 +
280 + return ! empty( $host_a ) && ! empty( $host_b ) && \strtolower( $host_a ) === \strtolower( $host_b );
281 +}
282 +
283 +/**
284 + * Whether an object is served under its own canonical id.
285 + *
286 + * An object is only trustworthy to cache when its own `id` is the URL it was
287 + * actually served from: otherwise one host could serve a document — and its
288 + * public key — under another host's id. Reads the raw `id` attribute only
289 + * (never the `url`/`href` fallback that object_to_uri() applies), because the
290 + * cache is keyed on `id`, so "is this canonical?" must ask the same field the
291 + * write uses. The comparison ignores the URL fragment and a trailing slash;
292 + * everything else (scheme, host, port, path, query) must match exactly.
293 + * Host-level equality is deliberately NOT enough — any different id on the same
294 + * host is still a distinct cache entry that a document served elsewhere must not write.
295 + *
296 + * @param array|string $item The fetched object, or its id.
297 + * @param string $url The URL the object was served from.
298 + *
299 + * @return bool True when the object's id is the canonical URL it was served from.
300 + */
301 +function id_matches_url( $item, $url ) {
302 + if ( \is_array( $item ) ) {
303 + $id = isset( $item['id'] ) && \is_string( $item['id'] ) ? $item['id'] : '';
304 + } elseif ( \is_string( $item ) ) {
305 + $id = $item;
306 + } else {
307 + $id = '';
308 + }
309 +
310 + $id = \strip_fragment_from_url( $id );
311 + $url = \strip_fragment_from_url( (string) $url );
312 +
313 + if ( '' === $id || '' === $url ) {
314 + return false;
315 + }
316 +
317 + return \untrailingslashit( $id ) === \untrailingslashit( $url );
318 +}
319 +
320 +/**
236 321 * Check if an `$data` is an Activity.
237 322 *
238 323 * @see https://www.w3.org/ns/activitystreams#activities
239 324 *
@@ -246,9 +331,9 @@
246 331 * Filters the activity types.
247 332 *
248 333 * @param array $types The activity types.
249 334 */
250 - $types = apply_filters( 'activitypub_activity_types', Activity::TYPES );
335 + $types = \apply_filters( 'activitypub_activity_types', Activity::TYPES );
251 336
252 337 return _is_type_of( $data, $types );
253 338 }
254 339
@@ -286,9 +371,9 @@
286 371 * Filters the actor types.
287 372 *
288 373 * @param array $types The actor types.
289 374 */
290 - $types = apply_filters( 'activitypub_actor_types', Actor::TYPES );
375 + $types = \apply_filters( 'activitypub_actor_types', Actor::TYPES );
291 376
292 377 return _is_type_of( $data, $types );
293 378 }
294 379
@@ -306,9 +391,9 @@
306 391 * Filters the collection types.
307 392 *
308 393 * @param array $types The collection types.
309 394 */
310 - $types = apply_filters( 'activitypub_collection_types', array( 'Collection', 'OrderedCollection', 'CollectionPage', 'OrderedCollectionPage' ) );
395 + $types = \apply_filters( 'activitypub_collection_types', array( 'Collection', 'OrderedCollection', 'CollectionPage', 'OrderedCollectionPage' ) );
311 396
312 397 return _is_type_of( $data, $types );
313 398 }
314 399
@@ -320,18 +405,18 @@
320 405 *
321 406 * @return boolean True if $data is of one of the types, false otherwise.
322 407 */
323 408 function _is_type_of( $data, $types ) {
324 - if ( is_string( $data ) ) {
325 - return in_array( $data, $types, true );
409 + if ( \is_string( $data ) ) {
410 + return \in_array( $data, $types, true );
326 411 }
327 412
328 - if ( is_array( $data ) && isset( $data['type'] ) ) {
329 - return in_array( $data['type'], $types, true );
413 + if ( \is_array( $data ) && isset( $data['type'] ) ) {
414 + return \in_array( $data['type'], $types, true );
330 415 }
331 416
332 417 if ( $data instanceof Base_Object ) {
333 - return in_array( $data->get_type(), $types, true );
418 + return \in_array( $data->get_type(), $types, true );
334 419 }
335 420
336 421 return false;
337 422 }