# desktop-mode/0.9.0/includes/recycle-bin/capture.php

OpenStation: Desktop Windows, Dock &amp; Virtual Desktops for WP Admin, version 0.9.0. 150 lines.

- Page: https://pluginprobe.com/plugins/desktop-mode/0.9.0/code/includes/recycle-bin/capture.php
- Raw: https://pluginprobe.com/plugins/desktop-mode/0.9.0/raw/includes/recycle-bin/capture.php
- Modified: 2026-05-15T10:51:18+00:00

Line numbers below start at 1. Link to a line or a range by appending a fragment to the
page URL, for example `https://pluginprobe.com/plugins/desktop-mode/0.9.0/code/includes/recycle-bin/capture.php#L10-L20`.

```php
<?php
/**
 * Desktop Mode — Recycle Bin: capture.
 *
 * Stamps "who-deleted-what-when" metadata onto posts, pages, and
 * comments as they pass through `wp_trash_post()` / `trashed_comment`,
 * so the Recycle Bin window can show "deleted by Alice, 5 minutes ago"
 * without a second roundtrip.
 *
 * Media attachments are NOT auto-routed through Trash — they follow
 * vanilla WordPress behavior (permanent-delete on first click). To
 * enable trash for media, define `MEDIA_TRASH` as `true` in
 * `wp-config.php`; the Recycle Bin window will surface trashed
 * attachments automatically because `attachment` is in the default
 * `desktop_mode_recycle_bin_capture_post_types` list.
 *
 *   - `desktop_mode_recycle_bin_capture_post_types` controls which post
 *     types we listen for in the first place.
 *   - The `desktop_mode_recycle_bin_item_captured` action fires after a
 *     successful capture so plugins can mirror the event (audit log,
 *     external storage, etc.).
 *
 * @package WPDesktopMode
 * @since   0.19.0
 */

defined( 'ABSPATH' ) || exit;

/**
 * Default list of post types the recycle bin is willing to capture.
 *
 * Posts and pages already trash by default; the value of capturing them
 * here is consistency: every "deleted" thing flows through the same
 * filter pipeline, so a plugin that wants a single audit log can
 * subscribe once.
 *
 * @since 0.19.0
 *
 * @return string[]
 */
function desktop_mode_recycle_bin_capture_post_types() {
	$types = array( 'post', 'page', 'attachment' );

	/**
	 * Filter the post types the recycle bin tracks.
	 *
	 * Returning a list excluding `attachment` disables the soft-delete
	 * interception entirely — vanilla WordPress media deletion resumes.
	 *
	 * @since 0.19.0
	 *
	 * @param string[] $types Post types whose deletions the recycle bin tracks.
	 */
	$types = apply_filters( 'desktop_mode_recycle_bin_capture_post_types', $types );

	return array_values( array_filter( array_map( 'strval', (array) $types ) ) );
}

/**
 * Mirror the capture for posts/pages so the bin has a uniform record
 * of "who deleted this and when."
 *
 * Core's `wp_trash_post_meta` is set on every trash but it doesn't
 * include the user id — we stash that ourselves under a private meta
 * key so the table can show "deleted by Alice".
 *
 * @since 0.19.0
 *
 * @param int $post_id Post being trashed.
 */
function desktop_mode_recycle_bin_on_trash_post( $post_id ) {
	$post = get_post( $post_id );
	if ( ! $post ) {
		return;
	}
	if ( ! in_array( $post->post_type, desktop_mode_recycle_bin_capture_post_types(), true ) ) {
		return;
	}
	desktop_mode_recycle_bin_record_capture( $post_id );
}

/**
 * Persist who-deleted-what-when metadata for a single item.
 *
 * Stored under `_desktop_mode_trash_*` postmeta on the trashed post itself —
 * survives un-trash naturally because we only consult it while in the
 * bin, and gets cleaned up by core when the post is permanently
 * deleted via the standard postmeta cascade.
 *
 * @since 0.19.0
 *
 * @param int $post_id Post id being captured.
 */
function desktop_mode_recycle_bin_record_capture( $post_id ) {
	$user_id = get_current_user_id();
	$now_gmt = current_time( 'mysql', true );

	update_post_meta( $post_id, '_desktop_mode_trash_user_id', (int) $user_id );
	update_post_meta( $post_id, '_desktop_mode_trash_time_gmt', $now_gmt );

	/**
	 * Fires after the recycle bin records a capture.
	 *
	 * Use this to mirror the event into an external audit log or to
	 * extend the captured payload with custom postmeta.
	 *
	 * @since 0.19.0
	 *
	 * @param int    $post_id Post id that was captured.
	 * @param int    $user_id User id who triggered the capture.
	 * @param string $now_gmt MySQL-format GMT timestamp.
	 */
	do_action( 'desktop_mode_recycle_bin_item_captured', $post_id, $user_id, $now_gmt );
}

add_action( 'wp_trash_post', 'desktop_mode_recycle_bin_on_trash_post', 10, 1 );

/**
 * Capture deleted-by metadata for comments — symmetric to
 * `desktop_mode_recycle_bin_record_capture` but writing to `commentmeta`
 * instead of `postmeta`. Comments use `wp_set_comment_status('trash')`
 * which fires `trashed_comment`; we don't need to short-circuit
 * anything (core already routes to a real trash status), just
 * stamp the moment so the bin can show "by Alice, 5 minutes ago".
 *
 * @since 0.21.0
 *
 * @param int $comment_id Comment about to be trashed.
 */
function desktop_mode_recycle_bin_on_trash_comment( $comment_id ) {
	$comment_id = (int) $comment_id;
	$user_id    = get_current_user_id();
	$now_gmt    = current_time( 'mysql', true );

	update_comment_meta( $comment_id, '_desktop_mode_trash_user_id', $user_id );
	update_comment_meta( $comment_id, '_desktop_mode_trash_time_gmt', $now_gmt );

	/**
	 * Fires after the recycle bin records a comment capture.
	 *
	 * @since 0.21.0
	 *
	 * @param int    $comment_id Comment id that was captured.
	 * @param int    $user_id    User id who triggered the capture.
	 * @param string $now_gmt    MySQL-format GMT timestamp.
	 */
	do_action( 'desktop_mode_recycle_bin_comment_captured', $comment_id, $user_id, $now_gmt );
}
add_action( 'trashed_comment', 'desktop_mode_recycle_bin_on_trash_comment', 10, 1 );

```
