PluginProbe
OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin / 1.1.11
OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin v1.1.11
1.1.11 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 All 35 releases
← All changes | includes/desktop-files/downloads.php +76 -91 0.9.7 → 1.1.11 View file →
@@ -1,7 +1,7 @@
1 1 <?php
2 2 /**
3 - * Desktop Mode — authenticated downloads for stored files.
3 + * OpenStation — authenticated downloads for stored files.
4 4 *
5 5 * Two routes:
6 6 *
7 7 * GET /desktop-mode/v1/files/uploads/(?P<id>\d+)/download
@@ -26,10 +26,9 @@
26 26 *
27 27 * Not-found and no-access are both 404 — a download probe must not
28 28 * reveal that a file exists (the Drive behavior).
29 29 *
30 - * @package WPDesktopMode
31 - * @since 0.9.6
30 + * @package OpenStation
32 31 */
33 32
34 33 defined( 'ABSPATH' ) || exit;
35 34
@@ -34,37 +33,41 @@
34 33 defined( 'ABSPATH' ) || exit;
35 34
36 35 /**
37 36 * Register the download routes.
38 - *
39 - * @since 0.9.6
40 37 */
41 -function desktop_mode_files_register_download_rest_routes() {
38 +function openstation_files_register_download_rest_routes() {
42 39 $ns = 'desktop-mode/v1';
43 - register_rest_route( $ns, '/files/uploads/(?P<id>\d+)/download', array(
44 - 'methods' => WP_REST_Server::READABLE,
45 - 'permission_callback' => 'desktop_mode_files_rest_permission',
46 - 'callback' => 'desktop_mode_files_rest_download_file',
47 - ) );
48 - register_rest_route( $ns, '/files/folders/(?P<id>\d+)/download', array(
49 - 'methods' => WP_REST_Server::READABLE,
50 - 'permission_callback' => 'desktop_mode_files_rest_permission',
51 - 'callback' => 'desktop_mode_files_rest_download_folder_zip',
52 - ) );
40 + register_rest_route(
41 + $ns,
42 + '/files/uploads/(?P<id>\d+)/download',
43 + array(
44 + 'methods' => WP_REST_Server::READABLE,
45 + 'permission_callback' => 'openstation_files_rest_permission',
46 + 'callback' => 'openstation_files_rest_download_file',
47 + )
48 + );
49 + register_rest_route(
50 + $ns,
51 + '/files/folders/(?P<id>\d+)/download',
52 + array(
53 + 'methods' => WP_REST_Server::READABLE,
54 + 'permission_callback' => 'openstation_files_rest_permission',
55 + 'callback' => 'openstation_files_rest_download_folder_zip',
56 + )
57 + );
53 58 }
54 -add_action( 'rest_api_init', 'desktop_mode_files_register_download_rest_routes' );
59 +add_action( 'rest_api_init', 'openstation_files_register_download_rest_routes' );
55 60
56 61 /**
57 62 * The masked not-found error shared by every failure path that
58 63 * must not leak existence.
59 64 *
60 - * @since 0.9.6
61 - *
62 65 * @return WP_Error
63 66 */
64 -function desktop_mode_files_download_not_found() {
67 +function openstation_files_download_not_found() {
65 68 return new WP_Error(
66 - 'desktop_mode_files_not_found',
69 + 'openstation_files_not_found',
67 70 __( 'File not found.', 'desktop-mode' ),
68 71 array( 'status' => 404 )
69 72 );
70 73 }
@@ -71,36 +74,32 @@
71 74
72 75 /**
73 76 * GET /files/uploads/<id>/download
74 77 *
75 - * @since 0.9.6
76 - *
77 78 * @param WP_REST_Request $req Request.
78 79 * @return WP_REST_Response|WP_Error
79 80 */
80 -function desktop_mode_files_rest_download_file( WP_REST_Request $req ) {
81 +function openstation_files_rest_download_file( WP_REST_Request $req ) {
81 82 $file_id = (int) $req['id'];
82 83 $user_id = get_current_user_id();
83 - $row = desktop_mode_stored_files_get( $file_id );
84 - if ( ! $row || ! desktop_mode_stored_file_user_can_read( $file_id, $user_id ) ) {
85 - return desktop_mode_files_download_not_found();
84 + $row = openstation_stored_files_get( $file_id );
85 + if ( ! $row || ! openstation_stored_file_user_can_read( $file_id, $user_id ) ) {
86 + return openstation_files_download_not_found();
86 87 }
87 - $path = desktop_mode_stored_file_path( $row );
88 + $path = openstation_stored_file_path( $row );
88 89 if ( ! $path || ! file_exists( $path ) ) {
89 - return desktop_mode_files_download_not_found();
90 + return openstation_files_download_not_found();
90 91 }
91 92
92 93 /**
93 94 * Fires when a stored-file download is about to be served.
94 95 *
95 - * @since 0.9.6
96 - *
97 96 * @param int $file_id Stored-file id.
98 97 * @param int $user_id Downloader.
99 98 */
100 - do_action( 'desktop_mode_stored_file_downloaded', $file_id, $user_id );
99 + do_action( 'openstation_stored_file_downloaded', $file_id, $user_id );
101 100
102 - return desktop_mode_files_download_stream_response(
101 + return openstation_files_download_stream_response(
103 102 $path,
104 103 $row['display_name'],
105 104 $row['mime'],
106 105 false
@@ -109,32 +108,30 @@
109 108
110 109 /**
111 110 * GET /files/folders/<id>/download — zip the folder's stored files.
112 111 *
113 - * @since 0.9.6
114 - *
115 112 * @param WP_REST_Request $req Request.
116 113 * @return WP_REST_Response|WP_Error
117 114 */
118 -function desktop_mode_files_rest_download_folder_zip( WP_REST_Request $req ) {
115 +function openstation_files_rest_download_folder_zip( WP_REST_Request $req ) {
119 116 $folder_id = (int) $req['id'];
120 117 $user_id = get_current_user_id();
121 - $folder = desktop_mode_files_get_folder( $folder_id );
118 + $folder = openstation_files_get_folder( $folder_id );
122 119 if ( ! $folder ) {
123 - return desktop_mode_files_download_not_found();
120 + return openstation_files_download_not_found();
124 121 }
125 122 $is_owner = (int) $folder['owner_id'] === $user_id;
126 123 if ( ! $is_owner ) {
127 - $cap = function_exists( 'desktop_mode_folder_share_user_capability' )
128 - ? desktop_mode_folder_share_user_capability( $folder_id, $user_id )
124 + $cap = function_exists( 'openstation_folder_share_user_capability' )
125 + ? openstation_folder_share_user_capability( $folder_id, $user_id )
129 126 : 'none';
130 127 if ( 'none' === $cap ) {
131 - return desktop_mode_files_download_not_found();
128 + return openstation_files_download_not_found();
132 129 }
133 130 }
134 131 if ( ! class_exists( 'ZipArchive' ) ) {
135 132 return new WP_Error(
136 - 'desktop_mode_stored_files_no_zip',
133 + 'openstation_stored_files_no_zip',
137 134 __( 'Folder download requires the PHP zip extension.', 'desktop-mode' ),
138 135 array( 'status' => 501 )
139 136 );
140 137 }
@@ -143,9 +140,9 @@
143 140 'entries' => array(), // path-in-zip => absolute path.
144 141 'empty_dirs' => array(), // path-in-zip (with trailing /).
145 142 'total_bytes' => 0,
146 143 );
147 - $result = desktop_mode_files_collect_zip_entries( $folder_id, $user_id, '', $manifest, array( $folder_id => true ), 0 );
144 + $result = openstation_files_collect_zip_entries( $folder_id, $user_id, '', $manifest, array( $folder_id => true ), 0 );
148 145 if ( is_wp_error( $result ) ) {
149 146 return $result;
150 147 }
151 148 $manifest = $result;
@@ -150,11 +147,11 @@
150 147 }
151 148 $manifest = $result;
152 149
153 150 require_once ABSPATH . 'wp-admin/includes/file.php';
154 - $tmp = wp_tempnam( 'desktop-mode-folder-zip' );
151 + $tmp = wp_tempnam( 'os-folder-zip' );
155 152 if ( ! $tmp ) {
156 - return new WP_Error( 'desktop_mode_stored_files_zip_failed', __( 'Could not create the archive.', 'desktop-mode' ), array( 'status' => 500 ) );
153 + return new WP_Error( 'openstation_stored_files_zip_failed', __( 'Could not create the archive.', 'desktop-mode' ), array( 'status' => 500 ) );
157 154 }
158 155 // Belt and braces for aborted connections — the normal path
159 156 // deletes right after streaming.
160 157 register_shutdown_function( 'wp_delete_file', $tmp );
@@ -161,9 +158,9 @@
161 158
162 159 $zip = new ZipArchive();
163 160 if ( true !== $zip->open( $tmp, ZipArchive::OVERWRITE ) ) {
164 161 wp_delete_file( $tmp );
165 - return new WP_Error( 'desktop_mode_stored_files_zip_failed', __( 'Could not create the archive.', 'desktop-mode' ), array( 'status' => 500 ) );
162 + return new WP_Error( 'openstation_stored_files_zip_failed', __( 'Could not create the archive.', 'desktop-mode' ), array( 'status' => 500 ) );
166 163 }
167 164 foreach ( $manifest['empty_dirs'] as $dir_entry ) {
168 165 $zip->addEmptyDir( $dir_entry );
169 166 }
@@ -178,24 +175,22 @@
178 175 }
179 176 }
180 177 if ( ! $zip->close() ) {
181 178 wp_delete_file( $tmp );
182 - return new WP_Error( 'desktop_mode_stored_files_zip_failed', __( 'Could not finish the archive (disk full?).', 'desktop-mode' ), array( 'status' => 500 ) );
179 + return new WP_Error( 'openstation_stored_files_zip_failed', __( 'Could not finish the archive (disk full?).', 'desktop-mode' ), array( 'status' => 500 ) );
183 180 }
184 181
185 182 /**
186 183 * Fires when a folder-zip download is about to be served.
187 184 *
188 - * @since 0.9.6
189 - *
190 185 * @param int $folder_id Folder id.
191 186 * @param int $user_id Downloader.
192 187 * @param int $count Number of files in the archive.
193 188 */
194 - do_action( 'desktop_mode_folder_zip_downloaded', $folder_id, $user_id, count( $manifest['entries'] ) );
189 + do_action( 'openstation_folder_zip_downloaded', $folder_id, $user_id, count( $manifest['entries'] ) );
195 190
196 191 $zip_name = sanitize_file_name( '' !== (string) $folder['name'] ? (string) $folder['name'] : 'folder' ) . '.zip';
197 - return desktop_mode_files_download_stream_response( $tmp, $zip_name, 'application/zip', true );
192 + return openstation_files_download_stream_response( $tmp, $zip_name, 'application/zip', true );
198 193 }
199 194
200 195 /**
201 196 * Recursive manifest collector. Walks the folder's placements
@@ -202,20 +197,19 @@
202 197 * (shared-namespace: every owner's rows), adds stored files the
203 198 * viewer can read, recurses into sub-folders, records empty
204 199 * directories, and enforces the caps.
205 200 *
206 - * @since 0.9.6
207 201 * @internal
208 202 *
209 - * @param int $folder_id Folder to walk.
210 - * @param int $user_id Viewer.
203 + * @param int $folder_id Folder to walk.
204 + * @param int $user_id Viewer.
211 205 * @param string $prefix Path prefix inside the zip ('' at root).
212 - * @param array $manifest Accumulator (entries / empty_dirs / total_bytes).
213 - * @param array $visited Folder ids already on the walk path (cycle guard).
214 - * @param int $depth Current depth.
206 + * @param array $manifest Accumulator (entries / empty_dirs / total_bytes).
207 + * @param array $visited Folder ids already on the walk path (cycle guard).
208 + * @param int $depth Current depth.
215 209 * @return array|WP_Error The updated manifest.
216 210 */
217 -function desktop_mode_files_collect_zip_entries( $folder_id, $user_id, $prefix, $manifest, $visited, $depth ) {
211 +function openstation_files_collect_zip_entries( $folder_id, $user_id, $prefix, $manifest, $visited, $depth ) {
218 212 if ( $depth > 32 ) {
219 213 return $manifest; // Depth cap — quietly stop descending.
220 214 }
221 215
@@ -222,14 +216,12 @@
222 216 /**
223 217 * Filters the zip caps. `max_entries` bounds file count,
224 218 * `max_bytes` bounds the SUM of input sizes.
225 219 *
226 - * @since 0.9.6
227 - *
228 220 * @param array $caps `{ max_entries: int, max_bytes: int }`.
229 221 */
230 222 $caps = (array) apply_filters(
231 - 'desktop_mode_stored_files_zip_caps',
223 + 'openstation_stored_files_zip_caps',
232 224 array(
233 225 'max_entries' => 1000,
234 226 'max_bytes' => 500 * MB_IN_BYTES,
235 227 )
@@ -234,9 +226,9 @@
234 226 'max_bytes' => 500 * MB_IN_BYTES,
235 227 )
236 228 );
237 229
238 - $rows = desktop_mode_files_get_for_user_folder( $user_id, $folder_id );
230 + $rows = openstation_files_get_for_user_folder( $user_id, $folder_id );
239 231 $used_names = array(); // lowercase name => count, per directory.
240 232 $had_child = false;
241 233
242 234 foreach ( $rows as $row ) {
@@ -244,13 +236,13 @@
244 236 $sub_id = (int) $row['file_ref'];
245 237 if ( $sub_id <= 0 || isset( $visited[ $sub_id ] ) ) {
246 238 continue;
247 239 }
248 - $sub = desktop_mode_files_get_folder( $sub_id );
240 + $sub = openstation_files_get_folder( $sub_id );
249 241 if ( ! $sub ) {
250 242 continue;
251 243 }
252 - $dir_name = desktop_mode_files_zip_unique_name(
244 + $dir_name = openstation_files_zip_unique_name(
253 245 sanitize_file_name( '' !== (string) $sub['name'] ? (string) $sub['name'] : 'folder' ),
254 246 $used_names
255 247 );
256 248 $had_child = true;
@@ -255,9 +247,9 @@
255 247 );
256 248 $had_child = true;
257 249 $visited[ $sub_id ] = true;
258 250 $before = count( $manifest['entries'] ) + count( $manifest['empty_dirs'] );
259 - $manifest = desktop_mode_files_collect_zip_entries( $sub_id, $user_id, $prefix . $dir_name . '/', $manifest, $visited, $depth + 1 );
251 + $manifest = openstation_files_collect_zip_entries( $sub_id, $user_id, $prefix . $dir_name . '/', $manifest, $visited, $depth + 1 );
260 252 if ( is_wp_error( $manifest ) ) {
261 253 return $manifest;
262 254 }
263 255 if ( count( $manifest['entries'] ) + count( $manifest['empty_dirs'] ) === $before ) {
@@ -270,18 +262,18 @@
270 262 if ( 'upload' !== $row['file_type'] ) {
271 263 continue; // References are not bytes; skipped by design.
272 264 }
273 265 $file_id = (int) $row['file_ref'];
274 - $file = desktop_mode_stored_files_get( $file_id );
275 - if ( ! $file || ! desktop_mode_stored_file_user_can_read( $file_id, $user_id ) ) {
266 + $file = openstation_stored_files_get( $file_id );
267 + if ( ! $file || ! openstation_stored_file_user_can_read( $file_id, $user_id ) ) {
276 268 continue;
277 269 }
278 - $path = desktop_mode_stored_file_path( $file );
270 + $path = openstation_stored_file_path( $file );
279 271 if ( ! $path || ! file_exists( $path ) ) {
280 272 continue;
281 273 }
282 274 $had_child = true;
283 - $entry_name = desktop_mode_files_zip_unique_name(
275 + $entry_name = openstation_files_zip_unique_name(
284 276 sanitize_file_name( '' !== $file['display_name'] ? $file['display_name'] : 'file' ),
285 277 $used_names
286 278 );
287 279
@@ -287,9 +279,9 @@
287 279
288 280 $manifest['total_bytes'] += (int) $file['size_bytes'];
289 281 if ( count( $manifest['entries'] ) + 1 > (int) $caps['max_entries'] ) {
290 282 return new WP_Error(
291 - 'desktop_mode_stored_files_zip_too_big',
283 + 'openstation_stored_files_zip_too_big',
292 284 __( 'This folder has too many files to download as one archive.', 'desktop-mode' ),
293 285 array( 'status' => 400 )
294 286 );
295 287 }
@@ -294,9 +286,9 @@
294 286 );
295 287 }
296 288 if ( (int) $caps['max_bytes'] > 0 && $manifest['total_bytes'] > (int) $caps['max_bytes'] ) {
297 289 return new WP_Error(
298 - 'desktop_mode_stored_files_zip_too_big',
290 + 'openstation_stored_files_zip_too_big',
299 291 __( 'This folder is too large to download as one archive.', 'desktop-mode' ),
300 292 array( 'status' => 400 )
301 293 );
302 294 }
@@ -315,9 +307,8 @@
315 307 * Per-directory case-insensitive dedupe: `report.pdf`,
316 308 * `Report.pdf` → `report.pdf`, `Report (2).pdf` so extraction on
317 309 * case-folding filesystems (Windows, macOS) never collides.
318 310 *
319 - * @since 0.9.6
320 311 * @internal
321 312 *
322 313 * @param string $name Sanitized candidate name.
323 314 * @param array $used_names By-ref lowercase tally for the directory.
@@ -322,15 +313,15 @@
322 313 * @param string $name Sanitized candidate name.
323 314 * @param array $used_names By-ref lowercase tally for the directory.
324 315 * @return string
325 316 */
326 -function desktop_mode_files_zip_unique_name( $name, &$used_names ) {
317 +function openstation_files_zip_unique_name( $name, &$used_names ) {
327 318 $key = strtolower( $name );
328 319 if ( ! isset( $used_names[ $key ] ) ) {
329 320 $used_names[ $key ] = 1;
330 321 return $name;
331 322 }
332 - $used_names[ $key ]++;
323 + ++$used_names[ $key ];
333 324 $n = $used_names[ $key ];
334 325 $dot = strrpos( $name, '.' );
335 326 if ( false === $dot || 0 === $dot ) {
336 327 return $name . " ($n)";
@@ -342,9 +333,8 @@
342 333 * Build the marker response the `rest_pre_serve_request` filter
343 334 * streams. The marker payload never reaches the client — the
344 335 * filter takes over the output entirely.
345 336 *
346 - * @since 0.9.6
347 337 * @internal
348 338 *
349 339 * @param string $path Absolute file path.
350 340 * @param string $name Download filename shown to the user.
@@ -351,12 +341,12 @@
351 341 * @param string $mime MIME type ('' = octet-stream).
352 342 * @param bool $delete_after Delete `$path` after streaming (zip temp).
353 343 * @return WP_REST_Response
354 344 */
355 -function desktop_mode_files_download_stream_response( $path, $name, $mime, $delete_after ) {
345 +function openstation_files_download_stream_response( $path, $name, $mime, $delete_after ) {
356 346 return new WP_REST_Response(
357 347 array(
358 - '__desktop_mode_stream' => array(
348 + '__openstation_stream' => array(
359 349 'path' => (string) $path,
360 350 'name' => (string) $name,
361 351 'mime' => (string) $mime,
362 352 'delete_after' => (bool) $delete_after,
@@ -370,16 +360,14 @@
370 360 * `rest_pre_serve_request` short-circuit: stream the file the
371 361 * download callbacks resolved. Non-stream results (errors included)
372 362 * fall through to normal JSON serving.
373 363 *
374 - * @since 0.9.6
375 - *
376 364 * @param bool $served Whether the request is already served.
377 365 * @param WP_HTTP_Response $result Result to send.
378 366 * @param WP_REST_Request $request Request used.
379 367 * @return bool
380 368 */
381 -function desktop_mode_files_serve_download( $served, $result, $request ) {
369 +function openstation_files_serve_download( $served, $result, $request ) {
382 370 if ( $served || ! $result instanceof WP_HTTP_Response ) {
383 371 return $served;
384 372 }
385 373 $route = (string) $request->get_route();
@@ -386,18 +374,18 @@
386 374 if ( ! preg_match( '#^/desktop-mode/v1/files/(uploads|folders)/\d+/download$#', $route ) ) {
387 375 return $served;
388 376 }
389 377 $data = $result->get_data();
390 - if ( ! is_array( $data ) || empty( $data['__desktop_mode_stream'] ) || 200 !== $result->get_status() ) {
378 + if ( ! is_array( $data ) || empty( $data['__openstation_stream'] ) || 200 !== $result->get_status() ) {
391 379 return $served; // Error shapes serialize as normal JSON.
392 380 }
393 - $stream = $data['__desktop_mode_stream'];
381 + $stream = $data['__openstation_stream'];
394 382 $path = (string) $stream['path'];
395 383 if ( '' === $path || ! file_exists( $path ) || ! is_readable( $path ) ) {
396 384 return $served;
397 385 }
398 386
399 - desktop_mode_files_emit_download( $path, (string) $stream['name'], (string) $stream['mime'] );
387 + openstation_files_emit_download( $path, (string) $stream['name'], (string) $stream['mime'] );
400 388
401 389 if ( ! empty( $stream['delete_after'] ) ) {
402 390 wp_delete_file( $path );
403 391 }
@@ -402,15 +390,14 @@
402 390 wp_delete_file( $path );
403 391 }
404 392 return true;
405 393 }
406 -add_filter( 'rest_pre_serve_request', 'desktop_mode_files_serve_download', 10, 3 );
394 +add_filter( 'rest_pre_serve_request', 'openstation_files_serve_download', 10, 3 );
407 395
408 396 /**
409 397 * Send the headers and the bytes. Split out so PHPUnit can target
410 398 * the header/name logic without hijacking output.
411 399 *
412 - * @since 0.9.6
413 400 * @internal
414 401 *
415 402 * @param string $path Absolute file path.
416 403 * @param string $name Download filename.
@@ -415,9 +402,9 @@
415 402 * @param string $path Absolute file path.
416 403 * @param string $name Download filename.
417 404 * @param string $mime MIME type.
418 405 */
419 -function desktop_mode_files_emit_download( $path, $name, $mime ) {
406 +function openstation_files_emit_download( $path, $name, $mime ) {
420 407 $size = (int) filesize( $path );
421 408
422 409 // Kill every output buffer + compression layer so
423 410 // Content-Length stays exact and readfile streams instead of
@@ -455,13 +442,11 @@
455 442
456 443 /**
457 444 * Daily sweep of stale zip temp files (aborted downloads whose
458 445 * shutdown cleanup never ran).
459 - *
460 - * @since 0.9.6
461 446 */
462 -function desktop_mode_stored_files_sweep_zip_temps() {
463 - $entries = glob( trailingslashit( get_temp_dir() ) . 'desktop-mode-folder-zip*' );
447 +function openstation_stored_files_sweep_zip_temps() {
448 + $entries = glob( trailingslashit( get_temp_dir() ) . 'os-folder-zip*' );
464 449 foreach ( (array) $entries as $entry ) {
465 450 if ( ! is_file( $entry ) ) {
466 451 continue;
467 452 }
@@ -470,5 +455,5 @@
470 455 wp_delete_file( $entry );
471 456 }
472 457 }
473 458 }
474 -add_action( 'desktop_mode_files_daily_prune', 'desktop_mode_stored_files_sweep_zip_temps' );
459 +add_action( 'desktop_mode_files_daily_prune', 'openstation_stored_files_sweep_zip_temps' );