PluginProbe
OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin / 1.1.10
OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin v1.1.10
1.1.10 1.1.9 1.1.8 1.1.7 1.1.6 1.1.5 1.1.4 1.1.3 1.1.2 1.1.1 1.1.0 1.0.1 1.0.0 0.9.8 0.9.7 0.9.6 0.9.4 0.9.5 0.9.3 0.9.2 0.9.1 0.9.0 0.8.9 0.8.8 0.8.7 All 34 releases
desktop-mode / includes / desktop-files / cascade-cleanup.php

cascade-cleanup.php in OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin 1.1.10, at includes/desktop-files/cascade-cleanup.php

235 lines 7.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * OpenStation — Cascade cleanup of placements on entity trash.
4 *
5 * When a WordPress entity that a desktop shortcut points at is
6 * trashed or deleted, the matching placement rows are soft-trashed
7 * so the wallpaper stops painting tiles that resolve to nothing.
8 *
9 * Hooks cover every route an entity can leave the live set by:
10 *
11 * - `wp_trash_post` — post / page / attachment moved to
12 * WP trash (our drag-to-trash, our CMO,
13 * WP admin, WP-CLI all funnel through
14 * this).
15 * - `before_delete_post` — force-delete (bypasses the trash
16 * stage when `EMPTY_TRASH_DAYS === 0`
17 * or the caller passed `force_delete`).
18 * - `delete_attachment` — hard-delete path for attachments that
19 * skips `wp_trash_post`.
20 * - `deleted_user` — user account removed (with or without
21 * content reassignment).
22 *
23 * Implementation is a bulk UPDATE that bypasses the per-placement
24 * permission check used by user-initiated trash. The trashing
25 * action has already been authorized at the entity level (Core
26 * gates the `wp_trash_post` cap, `delete_users`, etc.); cascading
27 * to the desktop shortcuts is a downstream side-effect of that
28 * authorized action, not a fresh user gesture.
29 *
30 * Soft-trash semantics: `trashed_at_ms` is set but the row stays
31 * in the table. The placement surfaces in the Recycle Bin and the
32 * user can restore it from there (independent of the source entity's
33 * trash status — restoring a post does NOT auto-restore its shortcut).
34 *
35 * @package OpenStation
36 */
37
38 defined( 'ABSPATH' ) || exit;
39
40 /**
41 * Soft-trash every live placement pointing at the given entity.
42 *
43 * Iterates `wp_desktop_mode_file_placements` for rows with the
44 * matching `file_type` + `file_ref` whose `trashed_at_ms` is
45 * unset, and stamps the trash columns. Each affected placement
46 * fires `openstation_files_after_cascade_trash_placement` so
47 * plugins (and the live UI refresh path) can react.
48 *
49 * Idempotent: re-running with an already-trashed entity is a
50 * no-op because the `trashed_at_ms IS NULL` filter excludes
51 * placements that were trashed by a previous pass.
52 *
53 * Bulk-friendly: the function tolerates an entity with many
54 * placements across many users — the affected placement count
55 * is unbounded by the API but bounded in practice by the number
56 * of users who shortcutted the same entity.
57 *
58 * @param string $file_type File-type slug — `'post'`,
59 * `'attachment'`, `'user'`, plugin-
60 * defined. Must match
61 * {@see OpenStation_File::type()}.
62 * @param string|int $file_ref Entity ref (post id, user id, …).
63 * @return int Number of placements soft-trashed.
64 */
65 function openstation_files_cascade_trash_placements_for_entity( $file_type, $file_ref ) {
66 global $wpdb;
67 $file_type = (string) $file_type;
68 $file_ref = (string) $file_ref;
69 if ( '' === $file_type || '' === $file_ref ) {
70 return 0;
71 }
72 $tables = openstation_files_table_names();
73
74 // SELECT the affected rows up front so we can fire a per-row
75 // action without a second roundtrip. Restrict to live (not yet
76 // trashed) placements — the cascade is idempotent and we don't
77 // want to re-stamp `trashed_at_ms` on rows the user trashed
78 // individually before the source entity was trashed.
79 $rows = $wpdb->get_results(
80 $wpdb->prepare(
81 "SELECT id, owner_id, parent_id
82 FROM {$tables['placements']}
83 WHERE file_type = %s
84 AND file_ref = %s
85 AND trashed_at_ms IS NULL",
86 $file_type,
87 $file_ref
88 ),
89 ARRAY_A
90 );
91 if ( empty( $rows ) ) {
92 return 0;
93 }
94
95 $now = openstation_files_now_ms();
96 $trashed = 0;
97 foreach ( $rows as $row ) {
98 $placement_id = (int) $row['id'];
99 $owner_id = (int) $row['owner_id'];
100 $ancestry = openstation_files_capture_ancestry( (int) $row['parent_id'] );
101 // `trashed_meta` carries the ancestry (so a restore knows
102 // where to put the tile back) plus a cascade marker so the
103 // recycle-bin UI can distinguish entity-cascade rows from
104 // user-initiated trashes if a plugin ever wants to render
105 // the source ("Trashed because the post was trashed.").
106 $meta = wp_json_encode(
107 array(
108 'ancestry' => $ancestry,
109 'cascade' => array(
110 'reason' => $file_type . '_trashed',
111 'file_type' => $file_type,
112 'file_ref' => $file_ref,
113 ),
114 )
115 );
116 $ok = $wpdb->update(
117 $tables['placements'],
118 array(
119 'trashed_at_ms' => $now,
120 'trashed_by' => $owner_id,
121 'trashed_meta' => $meta,
122 'updated_at_ms' => $now,
123 ),
124 array( 'id' => $placement_id ),
125 array( '%d', '%d', '%s', '%d' ),
126 array( '%d' )
127 );
128 if ( false === $ok ) {
129 continue;
130 }
131 ++$trashed;
132
133 /**
134 * Fires after a placement has been cascade-trashed because
135 * its source entity was trashed.
136 *
137 * @param int $placement_id Placement id.
138 * @param int $owner_id Placement owner.
139 * @param string $file_type Source entity file type.
140 * @param string|int $file_ref Source entity ref.
141 */
142 do_action(
143 'openstation_files_after_cascade_trash_placement',
144 $placement_id,
145 $owner_id,
146 $file_type,
147 $file_ref
148 );
149 }
150
151 return $trashed;
152 }
153
154 /**
155 * Wrap a `(post_id)` Core action so it cascades against
156 * `file_type='post'`. Both `wp_trash_post` and `before_delete_post`
157 * pass the post id as the first arg.
158 *
159 * Attachments share the post-id space and CAN go through these
160 * hooks too — but attachment placements use `file_type='attachment'`
161 * (not `'post'`), so this wrapper would miss them. The separate
162 * `delete_attachment` hook below covers attachments.
163 *
164 * @param int $post_id Post id.
165 */
166 function openstation_files_cascade_on_post_trash( $post_id ) {
167 $post_id = (int) $post_id;
168 if ( $post_id <= 0 ) {
169 return;
170 }
171 $post = get_post( $post_id );
172 if ( ! $post instanceof WP_Post ) {
173 return;
174 }
175 // Attachments route to the `attachment` file-type, NOT `post`,
176 // so the post-keyed cascade would no-op on them. The dedicated
177 // `delete_attachment` hook below catches the hard-delete path;
178 // the soft-trash path for attachments (when `EMPTY_TRASH_DAYS`
179 // is non-zero) also fires `wp_trash_post` — we cover it by
180 // dispatching to the attachment cascade below.
181 if ( 'attachment' === $post->post_type ) {
182 openstation_files_cascade_trash_placements_for_entity(
183 'attachment',
184 (string) $post_id
185 );
186 return;
187 }
188 openstation_files_cascade_trash_placements_for_entity( 'post', (string) $post_id );
189 }
190
191 add_action( 'wp_trash_post', 'openstation_files_cascade_on_post_trash', 10, 1 );
192 add_action( 'before_delete_post', 'openstation_files_cascade_on_post_trash', 10, 1 );
193
194 /**
195 * Force-delete path for attachments — Core's `wp_delete_attachment()`
196 * fires this BEFORE the attachment row is gone, so `get_post()` is
197 * still resolvable for any plugin that needs the metadata.
198 *
199 * @param int $attachment_id Attachment id.
200 */
201 function openstation_files_cascade_on_attachment_delete( $attachment_id ) {
202 $attachment_id = (int) $attachment_id;
203 if ( $attachment_id <= 0 ) {
204 return;
205 }
206 openstation_files_cascade_trash_placements_for_entity(
207 'attachment',
208 (string) $attachment_id
209 );
210 }
211
212 add_action( 'delete_attachment', 'openstation_files_cascade_on_attachment_delete', 10, 1 );
213
214 /**
215 * User account removed — cascade-trash every shortcut whose
216 * `file_type='user', file_ref='<id>'` pointed at the gone user.
217 * Fires AFTER deletion (`deleted_user`) because user-deletion in
218 * Core involves reassigning content; we don't need the live user
219 * row, only its id.
220 *
221 * @param int $user_id Deleted user id.
222 */
223 function openstation_files_cascade_on_user_delete( $user_id ) {
224 $user_id = (int) $user_id;
225 if ( $user_id <= 0 ) {
226 return;
227 }
228 openstation_files_cascade_trash_placements_for_entity(
229 'user',
230 (string) $user_id
231 );
232 }
233
234 add_action( 'deleted_user', 'openstation_files_cascade_on_user_delete', 10, 1 );
235