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-post.php +40 -203 9.3.08.0.2 View file →
@@ -8,65 +8,54 @@
8 8 */
9 9
10 10 namespace Activitypub;
11 11
12 -use Activitypub\Collection\Remote_Posts;
12 +use Activitypub\Collection\Posts;
13 13
14 14 /**
15 - * Check whether ActivityPub processing should be skipped for this post.
15 + * Check if a post is disabled for ActivityPub.
16 16 *
17 - * Pipeline-level gate. Used by schedulers, transformers, and the outbox to
18 - * decide whether a post participates in federation processing at all.
19 - *
20 - * Intentionally returns `false` for posts that are undergoing a federation
21 - * lifecycle transition — e.g., a previously federated post whose visibility
22 - * was changed to private, or a previously deleted post that was restored —
23 - * so that the Delete or Create activity can still be emitted to notify
24 - * remote servers.
25 - *
26 - * DO NOT use this as a content-exposure gate for REST metadata, block
27 - * rendering, content-negotiated frontend JSON, or any other surface that
28 - * reveals a post's current content or existence to unauthenticated readers.
29 - * Use {@see is_post_publicly_queryable()} for those: it answers the simpler
30 - * "is this post currently public?" question with no lifecycle escape hatch.
31 - *
32 - * @see is_post_publicly_queryable() For the current-visibility gate used by
33 - * content-exposure surfaces.
34 - *
35 17 * @param mixed $post The post object or ID.
36 18 *
37 - * @return boolean True if ActivityPub processing should be skipped for this post, false otherwise.
19 + * @return boolean True if the post is disabled, false otherwise.
38 20 */
39 21 function is_post_disabled( $post ) {
40 - // Refuse empty input so `get_post()` doesn't silently resolve to the global $post.
41 - if ( empty( $post ) ) {
22 + $post = \get_post( $post );
23 + $disabled = false;
24 +
25 + if ( ! $post ) {
42 26 return true;
43 27 }
44 28
45 - $post = \get_post( $post );
29 + $visibility = \get_post_meta( $post->ID, 'activitypub_content_visibility', true );
30 + $is_local_or_private = in_array( $visibility, array( ACTIVITYPUB_CONTENT_VISIBILITY_LOCAL, ACTIVITYPUB_CONTENT_VISIBILITY_PRIVATE ), true );
46 31
47 - if ( ! $post ) {
48 - return true;
32 + // Only 'publish' is public. 'inherit' is allowed only for attachments.
33 + $is_public_status = 'publish' === $post->post_status ||
34 + ( 'inherit' === $post->post_status && 'attachment' === $post->post_type );
35 +
36 + if (
37 + $is_local_or_private ||
38 + ! \post_type_supports( $post->post_type, 'activitypub' ) ||
39 + ! $is_public_status ||
40 + ! empty( $post->post_password )
41 + ) {
42 + $disabled = true;
49 43 }
50 44
51 - $disabled = ! is_post_publicly_queryable( $post );
52 -
53 45 /*
54 - * Lifecycle-transition override.
55 - *
56 - * A previously federated post that has since been moved to any non-
57 - * publicly-queryable state (local/private visibility, non-public
58 - * status, password-protected, or whose post type no longer supports
59 - * federation) still needs the pipeline to run so it can emit a Delete
60 - * activity. A post that was deleted but later restored needs the
61 - * pipeline to emit Create. In both cases we flip the gate back open
62 - * even though the post is not currently publicly queryable.
46 + * Check for posts that need special handling.
47 + * Federated posts changed to local/private or non-public status need Delete activity.
48 + * Deleted posts restored to public need Create activity.
63 49 */
64 50 $object_state = get_wp_object_state( $post );
65 51
66 52 if (
67 53 ACTIVITYPUB_OBJECT_STATE_DELETED === $object_state ||
68 - ( ACTIVITYPUB_OBJECT_STATE_FEDERATED === $object_state && $disabled )
54 + (
55 + ACTIVITYPUB_OBJECT_STATE_FEDERATED === $object_state &&
56 + ( $is_local_or_private || ! $is_public_status )
57 + )
69 58 ) {
70 59 $disabled = false;
71 60 }
72 61
@@ -79,130 +68,8 @@
79 68 return \apply_filters( 'activitypub_is_post_disabled', $disabled, $post );
80 69 }
81 70
82 71 /**
83 - * Check whether a post's current content is publicly queryable via ActivityPub.
84 - *
85 - * Content-exposure gate. Use wherever a post's current content, metadata, or
86 - * mere existence could leak to an unauthenticated request. Unlike
87 - * {@see is_post_disabled()}, this function ignores the federation lifecycle
88 - * state: a post that was federated publicly and has since been made private,
89 - * local, trashed, or password-protected returns `false` here, even while its
90 - * Delete activity is still pending in the outbox.
91 - *
92 - * Use for: per-post REST metadata routes (reactions, replies, context,
93 - * remote-reply), block server-side render callbacks that expose post
94 - * content, content-negotiated frontend JSON. Do NOT use for federation
95 - * pipeline decisions — that's what {@see is_post_disabled()} is for.
96 - *
97 - * A post is publicly queryable when it satisfies ALL of the following:
98 - * - `post_status` is `publish` (or a well-defined equivalent: published
99 - * attachments inheriting from a public parent, or a preview requested
100 - * by a user with edit capability).
101 - * - Its `activitypub_content_visibility` meta is neither `local` nor
102 - * `private`.
103 - * - The post type supports the `activitypub` feature.
104 - * - No `post_password` is set.
105 - *
106 - * @since 8.1.0
107 - *
108 - * @see is_post_disabled() For the pipeline-level federation gate.
109 - *
110 - * @param mixed $post The post object or ID.
111 - *
112 - * @return boolean True if the post is currently publicly queryable, false otherwise.
113 - */
114 -function is_post_publicly_queryable( $post ) {
115 - /*
116 - * Refuse to resolve an empty/zero input through `get_post()`. A bare
117 - * `get_post( null )` or `get_post( 0 )` falls back to the global
118 - * `$post` during a WordPress loop, which would silently check the
119 - * wrong post and potentially leak reactions/replies/metadata for a
120 - * looped-over post instead of the one the caller intended.
121 - */
122 - if ( empty( $post ) ) {
123 - return false;
124 - }
125 -
126 - $post = \get_post( $post );
127 -
128 - if ( ! $post ) {
129 - return false;
130 - }
131 -
132 - $visibility = \get_post_meta( $post->ID, 'activitypub_content_visibility', true );
133 - $is_local_or_private = \in_array( $visibility, array( ACTIVITYPUB_CONTENT_VISIBILITY_LOCAL, ACTIVITYPUB_CONTENT_VISIBILITY_PRIVATE ), true );
134 -
135 - /*
136 - * An attachment (`inherit` status) inherits its parent's visibility.
137 - * Recurse into the parent so the attachment also picks up the parent's
138 - * content-visibility meta, password protection, and post-type support,
139 - * not just its post_status. Unattached attachments are allowed through.
140 - */
141 - $is_attachment_public = 'inherit' === $post->post_status &&
142 - 'attachment' === $post->post_type &&
143 - ( ! $post->post_parent || is_post_publicly_queryable( $post->post_parent ) );
144 -
145 - // Draft, pending, and scheduled posts are allowed during preview requests so the Fediverse Preview works.
146 - $is_preview = \in_array( $post->post_status, array( 'draft', 'pending', 'future' ), true ) &&
147 - \get_query_var( 'preview' ) &&
148 - \current_user_can( 'edit_post', $post->ID );
149 -
150 - $is_public_status = 'publish' === $post->post_status || $is_attachment_public || $is_preview;
151 -
152 - $queryable = $is_public_status &&
153 - ! $is_local_or_private &&
154 - \post_type_supports( $post->post_type, 'activitypub' ) &&
155 - empty( $post->post_password );
156 -
157 - /**
158 - * Filter whether a post is publicly queryable via ActivityPub.
159 - *
160 - * @since 8.1.0
161 - *
162 - * @param boolean $queryable True if the post is publicly queryable, false otherwise.
163 - * @param \WP_Post $post The post object.
164 - */
165 - return \apply_filters( 'activitypub_is_post_publicly_queryable', $queryable, $post );
166 -}
167 -
168 -/**
169 - * Check whether a post is federated.
170 - *
171 - * A post is federated when it has been sent to the Fediverse (its federation
172 - * state is "federated") AND it is still publicly queryable, i.e. its post type
173 - * is enabled for ActivityPub, it has a public status, it is not password-
174 - * protected, and its content visibility allows it (see `is_post_publicly_queryable()`).
175 - *
176 - * Re-checking the live queryability alongside the stored state guards against a
177 - * stale "federated" status left behind when a post is moved to a private status,
178 - * switched to local visibility, or its post type loses ActivityPub support.
179 - *
180 - * The federation-state check also keeps `is_post_publicly_queryable()`'s
181 - * preview allowance inert here: a draft/pending/scheduled post is never in the
182 - * federated state, so the preview branch can never make this return true.
183 - *
184 - * @since 9.0.0
185 - *
186 - * @param mixed $post The post ID or object.
187 - *
188 - * @return boolean True if the post is federated, false otherwise.
189 - */
190 -function is_post_federated( $post ) {
191 - if ( empty( $post ) ) {
192 - return false;
193 - }
194 -
195 - $post = \get_post( $post );
196 -
197 - if ( ! $post ) {
198 - return false;
199 - }
200 -
201 - return ACTIVITYPUB_OBJECT_STATE_FEDERATED === get_wp_object_state( $post ) && is_post_publicly_queryable( $post );
202 -}
203 -
204 -/**
205 72 * Check if a post is an ActivityPub post.
206 73 *
207 74 * @param mixed $post The post object or ID.
208 75 *
@@ -215,9 +82,9 @@
215 82 return false;
216 83 }
217 84
218 85 // Check for ap_post post type.
219 - return Remote_Posts::POST_TYPE === $post->post_type;
86 + return Posts::POST_TYPE === $post->post_type;
220 87 }
221 88
222 89 /**
223 90 * Get the description of a post type.
@@ -234,9 +101,9 @@
234 101 case 'page':
235 102 $description = '';
236 103 break;
237 104 case 'attachment':
238 - $description = ' - ' . \__( 'Files uploaded to the media library (such as images, videos, documents, or other attachments). Note: This federates every file upload, not just published content.', 'activitypub' );
105 + $description = ' - ' . __( 'Files uploaded to the media library (such as images, videos, documents, or other attachments). Note: This federates every file upload, not just published content.', 'activitypub' );
239 106 break;
240 107 default:
241 108 $description = '';
242 109 if ( ! empty( $post_type->description ) ) {
@@ -250,42 +117,12 @@
250 117 * @param string $description The description of the post type.
251 118 * @param string $post_type_name The post type name.
252 119 * @param \WP_Post_Type $post_type The post type object.
253 120 */
254 - return \apply_filters( 'activitypub_post_type_description', $description, $post_type->name, $post_type );
121 + return apply_filters( 'activitypub_post_type_description', $description, $post_type->name, $post_type );
255 122 }
256 123
257 124 /**
258 - * Get the maximum number of media attachments a post may federate.
259 - *
260 - * A per-post limit wins over the site-wide setting, and the filter has the final say.
261 - *
262 - * @since 9.3.0
263 - *
264 - * @param int $post_id The post ID.
265 - *
266 - * @return int The maximum number of media attachments.
267 - */
268 -function get_max_attachments( $post_id ) {
269 - $max_media = \get_post_meta( $post_id, 'activitypub_max_image_attachments', true );
270 -
271 - if ( ! \is_numeric( $max_media ) ) {
272 - $max_media = \get_option( 'activitypub_max_image_attachments', ACTIVITYPUB_MAX_IMAGE_ATTACHMENTS );
273 - }
274 -
275 - /**
276 - * Filters the maximum number of media attachments allowed in a post.
277 - *
278 - * Despite the name suggesting only images, this filter controls the maximum number
279 - * of all media attachments (images, audio, and video) that can be included in an
280 - * ActivityPub post. The name is maintained for backwards compatibility.
281 - *
282 - * @param int $max_media Maximum number of media attachments. Default ACTIVITYPUB_MAX_IMAGE_ATTACHMENTS.
283 - */
284 - return (int) \apply_filters( 'activitypub_max_image_attachments', $max_media );
285 -}
286 -
287 -/**
288 125 * Get the enclosures of a post.
289 126 *
290 127 * @param int $post_id The post ID.
291 128 *
@@ -291,22 +128,22 @@
291 128 *
292 129 * @return array The enclosures.
293 130 */
294 131 function get_enclosures( $post_id ) {
295 - $enclosures = \get_post_meta( $post_id, 'enclosure', false );
132 + $enclosures = get_post_meta( $post_id, 'enclosure', false );
296 133
297 134 if ( ! $enclosures ) {
298 135 return array();
299 136 }
300 137
301 - $enclosures = \array_map(
138 + $enclosures = array_map(
302 139 static function ( $enclosure ) {
303 140 // Check if the enclosure is a string.
304 - if ( ! $enclosure || ! \is_string( $enclosure ) ) {
141 + if ( ! $enclosure || ! is_string( $enclosure ) ) {
305 142 return false;
306 143 }
307 144
308 - $attributes = \explode( "\n", $enclosure );
145 + $attributes = explode( "\n", $enclosure );
309 146
310 147 if ( ! isset( $attributes[0] ) || ! \wp_http_validate_url( $attributes[0] ) ) {
311 148 return false;
312 149 }
@@ -319,9 +156,9 @@
319 156 },
320 157 $enclosures
321 158 );
322 159
323 - return \array_filter( $enclosures );
160 + return array_filter( $enclosures );
324 161 }
325 162
326 163 /**
327 164 * Generates a summary of a post.
@@ -335,9 +172,9 @@
335 172 *
336 173 * @return string The generated post summary.
337 174 */
338 175 function generate_post_summary( $post, $length = 500 ) {
339 - $post = \get_post( $post );
176 + $post = get_post( $post );
340 177
341 178 if ( ! $post ) {
342 179 return '';
343 180 }
@@ -397,14 +234,14 @@
397 234 *
398 235 * @return string|false The content warning or false if not found.
399 236 */
400 237 function get_content_warning( $post_id ) {
401 - $post = \get_post( $post_id );
238 + $post = get_post( $post_id );
402 239 if ( ! $post ) {
403 240 return false;
404 241 }
405 242
406 - $warning = \get_post_meta( $post->ID, 'activitypub_content_warning', true );
243 + $warning = get_post_meta( $post->ID, 'activitypub_content_warning', true );
407 244 if ( empty( $warning ) ) {
408 245 return false;
409 246 }
410 247
@@ -437,9 +274,9 @@
437 274 *
438 275 * @return string|false The visibility of the post or false if not found.
439 276 */
440 277 function get_content_visibility( $post_id ) {
441 - $post = \get_post( $post_id );
278 + $post = get_post( $post_id );
442 279 if ( ! $post ) {
443 280 return false;
444 281 }
445 282
@@ -450,9 +287,9 @@
450 287 ACTIVITYPUB_CONTENT_VISIBILITY_PRIVATE,
451 288 ACTIVITYPUB_CONTENT_VISIBILITY_LOCAL,
452 289 );
453 290
454 - if ( \in_array( $visibility, $options, true ) ) {
291 + if ( in_array( $visibility, $options, true ) ) {
455 292 $_visibility = $visibility;
456 293 }
457 294
458 295 /**