get_results( $wpdb->prepare( "SELECT id, owner_id, parent_id FROM {$tables['placements']} WHERE file_type = %s AND file_ref = %s AND trashed_at_ms IS NULL", $file_type, $file_ref ), ARRAY_A ); if ( empty( $rows ) ) { return 0; } $now = desktop_mode_files_now_ms(); $trashed = 0; foreach ( $rows as $row ) { $placement_id = (int) $row['id']; $owner_id = (int) $row['owner_id']; $ancestry = desktop_mode_files_capture_ancestry( (int) $row['parent_id'] ); // `trashed_meta` carries the ancestry (so a restore knows // where to put the tile back) plus a cascade marker so the // recycle-bin UI can distinguish entity-cascade rows from // user-initiated trashes if a plugin ever wants to render // the source ("Trashed because the post was trashed."). $meta = wp_json_encode( array( 'ancestry' => $ancestry, 'cascade' => array( 'reason' => $file_type . '_trashed', 'file_type' => $file_type, 'file_ref' => $file_ref, ), ) ); $ok = $wpdb->update( $tables['placements'], array( 'trashed_at_ms' => $now, 'trashed_by' => $owner_id, 'trashed_meta' => $meta, 'updated_at_ms' => $now, ), array( 'id' => $placement_id ), array( '%d', '%d', '%s', '%d' ), array( '%d' ) ); if ( false === $ok ) { continue; } ++$trashed; /** * Fires after a placement has been cascade-trashed because * its source entity was trashed. * * @since 0.8.9 * * @param int $placement_id Placement id. * @param int $owner_id Placement owner. * @param string $file_type Source entity file type. * @param string|int $file_ref Source entity ref. */ do_action( 'desktop_mode_files_after_cascade_trash_placement', $placement_id, $owner_id, $file_type, $file_ref ); } return $trashed; } /** * Wrap a `(post_id)` Core action so it cascades against * `file_type='post'`. Both `wp_trash_post` and `before_delete_post` * pass the post id as the first arg. * * Attachments share the post-id space and CAN go through these * hooks too — but attachment placements use `file_type='attachment'` * (not `'post'`), so this wrapper would miss them. The separate * `delete_attachment` hook below covers attachments. * * @since 0.8.9 * * @param int $post_id Post id. */ function desktop_mode_files_cascade_on_post_trash( $post_id ) { $post_id = (int) $post_id; if ( $post_id <= 0 ) { return; } $post = get_post( $post_id ); if ( ! $post instanceof WP_Post ) { return; } // Attachments route to the `attachment` file-type, NOT `post`, // so the post-keyed cascade would no-op on them. The dedicated // `delete_attachment` hook below catches the hard-delete path; // the soft-trash path for attachments (when `EMPTY_TRASH_DAYS` // is non-zero) also fires `wp_trash_post` — we cover it by // dispatching to the attachment cascade below. if ( 'attachment' === $post->post_type ) { desktop_mode_files_cascade_trash_placements_for_entity( 'attachment', (string) $post_id ); return; } desktop_mode_files_cascade_trash_placements_for_entity( 'post', (string) $post_id ); } add_action( 'wp_trash_post', 'desktop_mode_files_cascade_on_post_trash', 10, 1 ); add_action( 'before_delete_post', 'desktop_mode_files_cascade_on_post_trash', 10, 1 ); /** * Force-delete path for attachments — Core's `wp_delete_attachment()` * fires this BEFORE the attachment row is gone, so `get_post()` is * still resolvable for any plugin that needs the metadata. * * @since 0.8.9 * * @param int $attachment_id Attachment id. */ function desktop_mode_files_cascade_on_attachment_delete( $attachment_id ) { $attachment_id = (int) $attachment_id; if ( $attachment_id <= 0 ) { return; } desktop_mode_files_cascade_trash_placements_for_entity( 'attachment', (string) $attachment_id ); } add_action( 'delete_attachment', 'desktop_mode_files_cascade_on_attachment_delete', 10, 1 ); /** * User account removed — cascade-trash every shortcut whose * `file_type='user', file_ref=''` pointed at the gone user. * Fires AFTER deletion (`deleted_user`) because user-deletion in * Core involves reassigning content; we don't need the live user * row, only its id. * * @since 0.8.9 * * @param int $user_id Deleted user id. */ function desktop_mode_files_cascade_on_user_delete( $user_id ) { $user_id = (int) $user_id; if ( $user_id <= 0 ) { return; } desktop_mode_files_cascade_trash_placements_for_entity( 'user', (string) $user_id ); } add_action( 'deleted_user', 'desktop_mode_files_cascade_on_user_delete', 10, 1 );