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/class-tombstone.php +28 -224 9.2.08.0.2 View file →
@@ -30,15 +30,8 @@
30 30 */
31 31 private static $codes = array( 404, 410 );
32 32
33 33 /**
34 - * The custom post type used to store local tombstones.
35 - *
36 - * @var string
37 - */
38 - const POST_TYPE = 'ap_tombstone';
39 -
40 - /**
41 34 * Check if a tombstone exists for the given resource.
42 35 *
43 36 * This is the main entry point for tombstone detection. It accepts various
44 37 * data types and routes them to the appropriate checking method:
@@ -103,9 +96,9 @@
103 96
104 97 return self::check_array( $data );
105 98 }
106 99
107 - if ( \in_array( (int) $response->get_error_code(), self::$codes, true ) ) {
100 + if ( in_array( (int) $response->get_error_code(), self::$codes, true ) ) {
108 101 return true;
109 102 }
110 103
111 104 return false;
@@ -113,38 +106,19 @@
113 106
114 107 /**
115 108 * Check if a local URL is tombstoned.
116 109 *
117 - * Matches by the MD5 hash of the normalized URL stored in `post_name`.
118 - * Falls back to the legacy `activitypub_tombstone_urls` option for
119 - * tombstones that have not yet been migrated.
110 + * Checks against the local tombstone URL registry stored in WordPress options.
111 + * Local URLs are normalized before comparison to ensure consistent matching.
120 112 *
121 113 * @param string $url The local URL to check for tombstone status.
122 114 *
123 - * @return bool True if the local URL is tombstoned, false otherwise.
115 + * @return bool True if the local URL is in the tombstone registry, false otherwise.
124 116 */
125 117 public static function exists_local( $url ) {
126 - if ( ! \is_string( $url ) || '' === $url ) {
127 - return false;
128 - }
118 + $urls = get_option( 'activitypub_tombstone_urls', array() );
129 119
130 - $normalized = normalize_url( $url );
131 -
132 - if ( ! empty( self::find_post_ids_by_url( $normalized ) ) ) {
133 - return true;
134 - }
135 -
136 - /*
137 - * Fallback to the legacy option during migration. Once the option is
138 - * deleted (migration complete), get_option returns false and the
139 - * is_array() guard short-circuits immediately.
140 - */
141 - $legacy = \get_option( 'activitypub_tombstone_urls', false );
142 - if ( \is_array( $legacy ) && \in_array( $normalized, $legacy, true ) ) {
143 - return true;
144 - }
145 -
146 - return false;
120 + return in_array( normalize_url( $url ), $urls, true );
147 121 }
148 122
149 123 /**
150 124 * Check if a WP_Error object indicates a tombstoned resource.
@@ -161,9 +135,9 @@
161 135 return false;
162 136 }
163 137
164 138 $data = $wp_error->get_error_data();
165 - if ( isset( $data['status'] ) && \in_array( (int) $data['status'], self::$codes, true ) ) {
139 + if ( isset( $data['status'] ) && in_array( (int) $data['status'], self::$codes, true ) ) {
166 140 return true;
167 141 }
168 142
169 143 return false;
@@ -218,92 +192,34 @@
218 192 return false;
219 193 }
220 194
221 195 /**
222 - * Look up tombstone post IDs by canonical URL.
223 - *
224 - * The MD5 of the normalized URL is unique per URL, so a successful
225 - * `bury()` produces exactly one row and the canonical lookup is enough.
226 - *
227 - * @since 8.3.0
228 - *
229 - * @param string $normalized The normalized URL (scheme stripped).
230 - * @return int[] Post IDs (zero or one entry under normal operation).
231 - */
232 - private static function find_post_ids_by_url( $normalized ) {
233 - global $wpdb;
234 -
235 - /*
236 - * `bury()` is idempotent on the MD5 slug, so a successful insert
237 - * produces exactly one row per URL. `LIMIT 1` matches that invariant
238 - * and keeps the query cheap on the hot `exists_local()` path.
239 - */
240 - // phpcs:ignore WordPress.DB.DirectDatabaseQuery
241 - $ids = $wpdb->get_col(
242 - $wpdb->prepare(
243 - "SELECT ID FROM {$wpdb->posts} WHERE post_type = %s AND post_name = %s LIMIT 1",
244 - self::POST_TYPE,
245 - \md5( $normalized )
246 - )
247 - );
248 -
249 - return \array_map( 'intval', $ids );
250 - }
251 -
252 - /**
253 196 * Add one or more URLs to the local tombstone registry.
254 197 *
255 198 * "Buries" URLs by adding them to the local tombstone URL registry.
256 - * URLs are normalized before storage; duplicate calls for the same URL
257 - * are a no-op because the `post_name` slug is the MD5 of the
258 - * normalized URL.
199 + * URLs are normalized before storage and duplicates are automatically removed.
200 + * This marks the URLs as tombstoned for future local checks.
259 201 *
260 202 * @param string ...$urls The URLs to add to the tombstone registry.
261 203 */
262 204 public static function bury( ...$urls ) {
205 + $to_add = array();
206 +
263 207 foreach ( $urls as $url ) {
264 - if ( ! \filter_var( $url, \FILTER_VALIDATE_URL ) ) {
265 - continue;
208 + if ( \filter_var( $url, \FILTER_VALIDATE_URL ) ) {
209 + $to_add[] = normalize_url( $url );
266 210 }
211 + }
267 212
268 - $normalized = normalize_url( $url );
213 + if ( empty( $to_add ) ) {
214 + return;
215 + }
269 216
270 - if ( ! empty( self::find_post_ids_by_url( $normalized ) ) ) {
271 - continue;
272 - }
217 + $stored_urls = \get_option( 'activitypub_tombstone_urls', array() );
218 + $stored_urls = \array_merge( $stored_urls, $to_add );
219 + $stored_urls = \array_unique( $stored_urls );
273 220
274 - /*
275 - * Store the original URL in `guid` so it is human-readable and
276 - * survives `esc_url()` without scheme mangling. The hash slug
277 - * in `post_name` is what we actually key lookups on.
278 - */
279 - $post_id = \wp_insert_post(
280 - array(
281 - 'post_type' => self::POST_TYPE,
282 - 'post_status' => 'publish',
283 - 'post_name' => \md5( $normalized ),
284 - 'guid' => $url,
285 - 'post_author' => 0,
286 - ),
287 - true
288 - );
289 -
290 - if ( \is_wp_error( $post_id ) || ! $post_id ) {
291 - /**
292 - * Fires when `bury()` fails to write a tombstone row.
293 - *
294 - * The URL is silently not tombstoned in this case — the
295 - * request path will respond as it would for any other
296 - * non-existent post. Useful as a monitoring hook.
297 - *
298 - * @since 8.3.0
299 - *
300 - * @param string $normalized The normalized URL that failed to bury.
301 - * @param \WP_Error|int|null $post_id The `wp_insert_post()` return value.
302 - */
303 - \do_action( 'activitypub_tombstone_bury_failed', $normalized, $post_id );
304 - }
305 - }
221 + \update_option( 'activitypub_tombstone_urls', $stored_urls );
306 222 }
307 223
308 224 /**
309 225 * Remove one or more URLs from the local tombstone registry.
@@ -314,133 +230,21 @@
314 230 *
315 231 * @param string ...$urls The URLs to remove from the tombstone registry.
316 232 */
317 233 public static function remove( ...$urls ) {
318 - $normalized_urls = array();
234 + $to_remove = array();
235 +
319 236 foreach ( $urls as $url ) {
320 237 if ( \filter_var( $url, \FILTER_VALIDATE_URL ) ) {
321 - $normalized_urls[] = normalize_url( $url );
238 + $to_remove[] = normalize_url( $url );
322 239 }
323 240 }
324 241
325 - if ( empty( $normalized_urls ) ) {
242 + if ( empty( $to_remove ) ) {
326 243 return;
327 244 }
328 245
329 - $normalized_urls = \array_values( \array_unique( $normalized_urls ) );
330 -
331 - foreach ( $normalized_urls as $normalized ) {
332 - foreach ( self::find_post_ids_by_url( $normalized ) as $post_id ) {
333 - \wp_delete_post( $post_id, true );
334 - }
335 - }
336 -
337 - $legacy = \get_option( 'activitypub_tombstone_urls', false );
338 - if ( ! \is_array( $legacy ) ) {
339 - return;
340 - }
341 -
342 - $filtered = \array_values( \array_diff( $legacy, $normalized_urls ) );
343 - if ( \count( $filtered ) === \count( $legacy ) ) {
344 - return;
345 - }
346 -
347 - if ( empty( $filtered ) ) {
348 - \delete_option( 'activitypub_tombstone_urls' );
349 - } else {
350 - \update_option( 'activitypub_tombstone_urls', $filtered );
351 - }
352 - }
353 -
354 - /**
355 - * Delete every tombstone post and the legacy option.
356 - *
357 - * Used during plugin uninstall to clean up all local tombstones.
358 - *
359 - * @since 8.3.0
360 - *
361 - * @return int The number of tombstone posts deleted.
362 - */
363 - public static function delete_all() {
364 - global $wpdb;
365 -
366 - $post_ids = \array_map(
367 - 'intval',
368 - // phpcs:ignore WordPress.DB.DirectDatabaseQuery
369 - $wpdb->get_col(
370 - $wpdb->prepare(
371 - "SELECT ID FROM {$wpdb->posts} WHERE post_type = %s",
372 - self::POST_TYPE
373 - )
374 - )
375 - );
376 -
377 - $deleted = 0;
378 - foreach ( $post_ids as $post_id ) {
379 - if ( \wp_delete_post( $post_id, true ) ) {
380 - ++$deleted;
381 - }
382 - }
383 -
384 - \delete_option( 'activitypub_tombstone_urls' );
385 -
386 - return $deleted;
387 - }
388 -
389 - /**
390 - * Delete tombstones older than the retention window.
391 - *
392 - * Processes up to `$batch_size` tombstones per call. Retention is
393 - * non-urgent: large backlogs drain across multiple daily runs of the
394 - * `activitypub_tombstone_purge` cron event.
395 - *
396 - * @since 8.3.0
397 - *
398 - * @param int $batch_size Max number of tombstones to delete per call.
399 - * @return int The number of tombstones deleted.
400 - */
401 - public static function purge( $batch_size = 200 ) {
402 - /**
403 - * Filters the retention window for local tombstones, in days.
404 - *
405 - * Set to 0 or a negative value to disable automatic purge.
406 - *
407 - * @since 8.3.0
408 - *
409 - * @param int $days Retention window in days. Default 90.
410 - */
411 - $days = (int) \apply_filters( 'activitypub_tombstone_retention_days', 90 );
412 -
413 - if ( $days <= 0 ) {
414 - return 0;
415 - }
416 -
417 - $cutoff = \gmdate( 'Y-m-d H:i:s', \time() - $days * DAY_IN_SECONDS );
418 -
419 - $ids = \get_posts(
420 - array(
421 - 'post_type' => self::POST_TYPE,
422 - 'post_status' => 'publish',
423 - 'posts_per_page' => (int) $batch_size,
424 - 'fields' => 'ids',
425 - 'orderby' => 'date',
426 - 'order' => 'ASC',
427 - 'no_found_rows' => true,
428 - 'date_query' => array(
429 - array(
430 - 'column' => 'post_date_gmt',
431 - 'before' => $cutoff,
432 - ),
433 - ),
434 - )
435 - );
436 -
437 - $deleted = 0;
438 - foreach ( $ids as $id ) {
439 - if ( \wp_delete_post( (int) $id, true ) ) {
440 - ++$deleted;
441 - }
442 - }
443 -
444 - return $deleted;
246 + $stored_urls = \get_option( 'activitypub_tombstone_urls', array() );
247 + $stored_urls = \array_diff( $stored_urls, $to_remove );
248 + \update_option( 'activitypub_tombstone_urls', $stored_urls );
445 249 }
446 250 }