PluginProbe
ActivityPub / 8.0.2
ActivityPub v8.0.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/functions-activity.php +18 -193 9.3.08.0.2 View file →
@@ -50,19 +50,9 @@
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 - // 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 ) );
54 + return \array_unique( $recipient_items );
65 55 }
66 56
67 57 /**
68 58 * Extract recipient URLs from a specific property of an Activity object.
@@ -102,21 +92,21 @@
102 92 * ACTIVITYPUB_CONTENT_VISIBILITY_PRIVATE.
103 93 */
104 94 function get_activity_visibility( $activity ) {
105 95 // Set default visibility for specific activity types.
106 - if ( ! empty( $activity['type'] ) && \in_array( $activity['type'], array( 'Accept', 'Delete', 'Follow', 'Reject', 'Undo' ), true ) ) {
96 + if ( ! empty( $activity['type'] ) && in_array( $activity['type'], array( 'Accept', 'Delete', 'Follow', 'Reject', 'Undo' ), true ) ) {
107 97 return ACTIVITYPUB_CONTENT_VISIBILITY_PRIVATE;
108 98 }
109 99
110 100 // Check 'to' field for public visibility.
111 101 $to = extract_recipients_from_activity_property( 'to', $activity );
112 - if ( ! empty( \array_intersect( $to, ACTIVITYPUB_PUBLIC_AUDIENCE_IDENTIFIERS ) ) ) {
102 + if ( ! empty( array_intersect( $to, ACTIVITYPUB_PUBLIC_AUDIENCE_IDENTIFIERS ) ) ) {
113 103 return ACTIVITYPUB_CONTENT_VISIBILITY_PUBLIC;
114 104 }
115 105
116 106 // Check 'cc' field for quiet public visibility.
117 107 $cc = extract_recipients_from_activity_property( 'cc', $activity );
118 - if ( ! empty( \array_intersect( $cc, ACTIVITYPUB_PUBLIC_AUDIENCE_IDENTIFIERS ) ) ) {
108 + if ( ! empty( array_intersect( $cc, ACTIVITYPUB_PUBLIC_AUDIENCE_IDENTIFIERS ) ) ) {
119 109 return ACTIVITYPUB_CONTENT_VISIBILITY_QUIET_PUBLIC;
120 110 }
121 111
122 112 return ACTIVITYPUB_CONTENT_VISIBILITY_PRIVATE;
@@ -142,9 +132,9 @@
142 132 if ( empty( $recipients ) ) {
143 133 return false;
144 134 }
145 135
146 - return ! empty( \array_intersect( $recipients, ACTIVITYPUB_PUBLIC_AUDIENCE_IDENTIFIERS ) );
136 + return ! empty( array_intersect( $recipients, ACTIVITYPUB_PUBLIC_AUDIENCE_IDENTIFIERS ) );
147 137 }
148 138
149 139 /**
150 140 * Check if passed Activity is a reply.
@@ -181,13 +171,13 @@
181 171 * @return string|null The URI of the ActivityPub object.
182 172 */
183 173 function object_to_uri( $data ) {
184 174 // Check whether it is already simple.
185 - if ( ! $data || \is_string( $data ) ) {
175 + if ( ! $data || is_string( $data ) ) {
186 176 return $data;
187 177 }
188 178
189 - if ( \is_object( $data ) ) {
179 + if ( is_object( $data ) ) {
190 180 $data = $data->to_array();
191 181 }
192 182
193 183 /*
@@ -193,14 +183,14 @@
193 183 /*
194 184 * Check if it is a list, then take first item.
195 185 * This plugin does not support collections.
196 186 */
197 - if ( \array_is_list( $data ) ) {
187 + if ( array_is_list( $data ) ) {
198 188 $data = $data[0];
199 189 }
200 190
201 191 // Check if it is simplified now.
202 - if ( \is_string( $data ) ) {
192 + if ( is_string( $data ) ) {
203 193 return $data;
204 194 }
205 195
206 196 $type = 'Object';
@@ -221,22 +211,10 @@
221 211 case 'Mention': // See https://www.w3.org/TR/activitystreams-vocabulary/#dfn-mention.
222 212 $data = $data['href'];
223 213 break;
224 214
225 - case 'FeaturedItem': // See https://github.com/mastodon/featured_collections/pull/1.
226 - $data = object_to_uri( $data['featuredObject'] ?? null );
227 - break;
228 -
229 215 default:
230 - if ( isset( $data['id'] ) ) {
231 - $data = $data['id'];
232 - } elseif ( isset( $data['url'] ) ) {
233 - $data = object_to_uri( $data['url'] );
234 - } elseif ( isset( $data['href'] ) ) {
235 - $data = $data['href'];
236 - } else {
237 - $data = null;
238 - }
216 + $data = $data['id'];
239 217 break;
240 218 }
241 219
242 220 return $data;
@@ -242,161 +220,8 @@
242 220 return $data;
243 221 }
244 222
245 223 /**
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 -/**
321 - * Normalize an actor URI so two spellings of the same identity compare equal.
322 - *
323 - * Folds only what RFC 3986 calls case-insensitive, the scheme and host, plus a default port
324 - * and a trailing slash, and drops the fragment. Path and query keep their case, and `http`
325 - * stays distinct from `https`. Userinfo is dropped, so a few technically distinct URIs compare
326 - * equal, which errs towards matching a block rather than missing one.
327 - *
328 - * Deliberately not used by `id_matches_url()`, which guards a cache write keyed on the exact
329 - * id: folding there would confirm a document under one spelling and store it under another.
330 - * A mismatch there is not a rejection, {@see \Activitypub\Http::get_remote_object()} re-fetches
331 - * the declared id and requires that to self-confirm, so the strictness costs one request rather
332 - * than refusing a document that spells its own host differently.
333 - *
334 - * @since 9.3.0
335 - *
336 - * @param string $uri The actor URI.
337 - *
338 - * @return string The normalized URI, or an empty string when there is nothing to compare.
339 - */
340 -function normalize_actor_uri( $uri ) {
341 - $uri = \is_string( $uri ) ? \trim( $uri ) : '';
342 -
343 - if ( '' === $uri ) {
344 - return '';
345 - }
346 -
347 - /*
348 - * Cut at the first `#`, which is the only place a fragment can start, rather than through
349 - * `strip_fragment_from_url()`, which rebuilds from parsed parts and so leaves a hostless
350 - * identifier alone. Without this a fragment on a handle survives normalization and
351 - * `acct:user@example.com#x` slips past a block on `acct:user@example.com`. The host branch
352 - * never re-appends one either way.
353 - */
354 - $fragment = \strpos( $uri, '#' );
355 -
356 - if ( false !== $fragment ) {
357 - $uri = \substr( $uri, 0, $fragment );
358 - }
359 -
360 - $parts = \wp_parse_url( $uri );
361 -
362 - /*
363 - * A handle has no parsable host, so its own host half is folded on its own. Anything else
364 - * without one is malformed and is compared as it came in.
365 - */
366 - if ( empty( $parts['host'] ) ) {
367 - // `acct:` and a leading `@` are spellings of the same handle, so they are dropped before
368 - // comparing. The local part keeps its case; only the host half is folded.
369 - $handle = \preg_replace( '/^acct:/i', '', $uri );
370 - $handle = \ltrim( $handle, '@' );
371 -
372 - if ( \preg_match( '/^(.*@)([^@]+)$/', $handle, $parsed ) ) {
373 - return $parsed[1] . fold_host( $parsed[2] );
374 - }
375 -
376 - return \untrailingslashit( $uri );
377 - }
378 -
379 - static $default = array(
380 - 'http' => 80,
381 - 'https' => 443,
382 - );
383 -
384 - $scheme = \strtolower( $parts['scheme'] ?? '' );
385 -
386 - // A port that is the scheme's default is the same address written two ways.
387 - $is_default = isset( $parts['port'] ) && \array_key_exists( $scheme, $default ) && $default[ $scheme ] === (int) $parts['port'];
388 - $port = isset( $parts['port'] ) && ! $is_default ? ':' . (int) $parts['port'] : '';
389 -
390 - // The trailing slash is folded on the path rather than the whole URI so a query cannot hide it.
391 - // A scheme-relative reference keeps its `//`; emitting `://` would match nothing.
392 - $authority = ( '' === $scheme ? '//' : $scheme . '://' ) . fold_host( $parts['host'] );
393 - $normalized = $authority . $port . \untrailingslashit( $parts['path'] ?? '' );
394 -
395 - return isset( $parts['query'] ) ? $normalized . '?' . $parts['query'] : $normalized;
396 -}
397 -
398 -/**
399 224 * Check if an `$data` is an Activity.
400 225 *
401 226 * @see https://www.w3.org/ns/activitystreams#activities
402 227 *
@@ -409,9 +234,9 @@
409 234 * Filters the activity types.
410 235 *
411 236 * @param array $types The activity types.
412 237 */
413 - $types = \apply_filters( 'activitypub_activity_types', Activity::TYPES );
238 + $types = apply_filters( 'activitypub_activity_types', Activity::TYPES );
414 239
415 240 return _is_type_of( $data, $types );
416 241 }
417 242
@@ -449,9 +274,9 @@
449 274 * Filters the actor types.
450 275 *
451 276 * @param array $types The actor types.
452 277 */
453 - $types = \apply_filters( 'activitypub_actor_types', Actor::TYPES );
278 + $types = apply_filters( 'activitypub_actor_types', Actor::TYPES );
454 279
455 280 return _is_type_of( $data, $types );
456 281 }
457 282
@@ -469,9 +294,9 @@
469 294 * Filters the collection types.
470 295 *
471 296 * @param array $types The collection types.
472 297 */
473 - $types = \apply_filters( 'activitypub_collection_types', array( 'Collection', 'OrderedCollection', 'CollectionPage', 'OrderedCollectionPage' ) );
298 + $types = apply_filters( 'activitypub_collection_types', array( 'Collection', 'OrderedCollection', 'CollectionPage', 'OrderedCollectionPage' ) );
474 299
475 300 return _is_type_of( $data, $types );
476 301 }
477 302
@@ -483,18 +308,18 @@
483 308 *
484 309 * @return boolean True if $data is of one of the types, false otherwise.
485 310 */
486 311 function _is_type_of( $data, $types ) {
487 - if ( \is_string( $data ) ) {
488 - return \in_array( $data, $types, true );
312 + if ( is_string( $data ) ) {
313 + return in_array( $data, $types, true );
489 314 }
490 315
491 - if ( \is_array( $data ) && isset( $data['type'] ) ) {
492 - return \in_array( $data['type'], $types, true );
316 + if ( is_array( $data ) && isset( $data['type'] ) ) {
317 + return in_array( $data['type'], $types, true );
493 318 }
494 319
495 320 if ( $data instanceof Base_Object ) {
496 - return \in_array( $data->get_type(), $types, true );
321 + return in_array( $data->get_type(), $types, true );
497 322 }
498 323
499 324 return false;
500 325 }