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/schema.php +80 -67 0.9.8 → 1.1.11 View file →
@@ -1,7 +1,7 @@
1 1 <?php
2 2 /**
3 - * Desktop Mode — Files-on-the-Desktop schema.
3 + * OpenStation — Files-on-the-Desktop schema.
4 4 *
5 5 * Five custom tables back the system:
6 6 *
7 7 * - `_desktop_mode_file_placements` — every (user, parent_folder,
@@ -31,24 +31,37 @@
31 31 * denies individually; the shares row itself stays `pending`).
32 32 *
33 33 * dbDelta is the only safe path for schema migrations against the
34 34 * Core tables environment — Phase 6 re-uses this file by bumping
35 - * `DESKTOP_MODE_FILES_SCHEMA_VERSION` and adding columns.
35 + * `OPENSTATION_FILES_SCHEMA_VERSION` and adding columns.
36 36 *
37 - * @package WPDesktopMode
37 + * @package OpenStation
38 38 */
39 39
40 40 defined( 'ABSPATH' ) || exit;
41 41
42 -define( 'DESKTOP_MODE_FILES_SCHEMA_VERSION', '13' );
43 -define( 'DESKTOP_MODE_FILES_SCHEMA_OPTION', 'desktop_mode_files_schema_version' );
42 +define( 'OPENSTATION_FILES_SCHEMA_VERSION', '13' );
43 +/**
44 + * The VALUE keeps its pre-rebrand spelling on purpose: it is a
45 + * persisted or externally-visible identifier, so renaming it would
46 + * orphan data already written by live installs (or break a live
47 + * URL). The mismatch between this constant's name and its value is
48 + * deliberate — it is NOT a half-finished rename.
49 + */
50 +define( 'OPENSTATION_FILES_SCHEMA_OPTION', 'desktop_mode_files_schema_version' );
44 51
45 52 /**
46 53 * Returns the per-table names with the active prefix applied.
47 54 *
55 + * The `desktop_mode_` segment is the pre-rebrand spelling and is frozen:
56 + * these are real tables holding real rows on live installs. Renaming
57 + * them silently creates a second, empty set and every desktop icon,
58 + * folder and uploaded file disappears. The mismatch against the
59 + * `openstation_*` function name is deliberate.
60 + *
48 61 * @return array{ placements: string, folders: string, tombstones: string, shares: string, decisions: string }
49 62 */
50 -function desktop_mode_files_table_names() {
63 +function openstation_files_table_names() {
51 64 global $wpdb;
52 65 return array(
53 66 'placements' => $wpdb->prefix . 'desktop_mode_file_placements',
54 67 'folders' => $wpdb->prefix . 'desktop_mode_folders',
@@ -63,15 +76,15 @@
63 76 * Idempotent `dbDelta` call. Hooked on plugin activation and on
64 77 * `admin_init` (gated by a version-option mismatch) so a manual
65 78 * file copy install still ends up with the tables.
66 79 */
67 -function desktop_mode_files_install_schema() {
80 +function openstation_files_install_schema() {
68 81 global $wpdb;
69 82
70 83 require_once ABSPATH . 'wp-admin/includes/upgrade.php';
71 84
72 - $tables = desktop_mode_files_table_names();
73 - $charset_collate = $wpdb->get_charset_collate();
85 + $tables = openstation_files_table_names();
86 + $charset_collate = $wpdb->get_charset_collate();
74 87
75 88 // Schema v2: adds trash columns to both placements
76 89 // and folders so deleted shortcuts and folders land in the
77 90 // recycle bin instead of vanishing. `trashed_at_ms` is the
@@ -181,9 +194,9 @@
181 194 // `owner_id` as a missing column and ADD it (leaving the old
182 195 // `user_id` in place + the new `owner_id` NULL). Running the
183 196 // CHANGE COLUMN first means dbDelta sees the table already
184 197 // matches the desired shape.
185 - desktop_mode_files_rename_user_id_to_owner_id();
198 + openstation_files_rename_user_id_to_owner_id();
186 199
187 200 dbDelta( $placements_sql );
188 201 dbDelta( $folders_sql );
189 202 dbDelta( $tombstones_sql );
@@ -192,9 +205,9 @@
192 205 // dbDelta has well-documented quirks with `NULL`-only columns
193 206 // (no DEFAULT) — under some MySQL/MariaDB combos it silently
194 207 // skips the ADD COLUMN. Verify the v2 trash columns are
195 208 // physically present and ALTER them in directly when not.
196 - desktop_mode_files_ensure_trash_columns();
209 + openstation_files_ensure_trash_columns();
197 210
198 211 // v4: clean up duplicate placements created by sessions that
199 212 // hit the auto-orphan-placer while the v2 trash columns were
200 213 // missing — every `WHERE trashed_at_ms IS NULL` precheck
@@ -201,15 +214,15 @@
201 214 // returned empty, so each pageload re-inserted every
202 215 // registered shortcut. Collapse runs of identical
203 216 // `(owner_id, parent_id, file_type, file_ref)` rows down to
204 217 // the lowest id.
205 - desktop_mode_files_dedupe_placements();
218 + openstation_files_dedupe_placements();
206 219
207 220 // v5: enforce uniqueness at the DB level so a future bug
208 221 // (or a racing pair of REST requests) can never re-create
209 222 // the duplicate shortcuts again. Must run AFTER dedupe —
210 223 // adding a unique key against duplicate rows would fail.
211 - desktop_mode_files_ensure_unique_placement_index();
224 + openstation_files_ensure_unique_placement_index();
212 225
213 226 // v9: belt-and-suspenders existence check for the shares +
214 227 // decisions tables. The folder-sharing feature is the
215 228 // canonical source of truth for "who can see this folder" —
@@ -215,10 +228,10 @@
215 228 // canonical source of truth for "who can see this folder" —
216 229 // the `share_meta` JSON column on the folders table remains
217 230 // for diagnostic purposes only and is not consulted by the
218 231 // visibility resolver.
219 - desktop_mode_files_ensure_shares_table();
220 - desktop_mode_files_ensure_decisions_table();
232 + openstation_files_ensure_shares_table();
233 + openstation_files_ensure_decisions_table();
221 234
222 235 // v10: `updated_by` column on placements so the If-Match 409
223 236 // conflict toast names the SESSION that actually won the race,
224 237 // not just whoever currently owns the row. Critical for the
@@ -224,11 +237,11 @@
224 237 // not just whoever currently owns the row. Critical for the
225 238 // shared-write scenario where User B (writer recipient) moves a
226 239 // placement and User C gets the conflict — without this column,
227 240 // the toast would blame User A (owner of the row).
228 - desktop_mode_files_ensure_updated_by_column();
241 + openstation_files_ensure_updated_by_column();
229 242
230 - update_option( DESKTOP_MODE_FILES_SCHEMA_OPTION, DESKTOP_MODE_FILES_SCHEMA_VERSION );
243 + update_option( OPENSTATION_FILES_SCHEMA_OPTION, OPENSTATION_FILES_SCHEMA_VERSION );
231 244
232 245 /**
233 246 * Fires after the files schema is installed / migrated.
234 247 *
@@ -233,9 +246,9 @@
233 246 * Fires after the files schema is installed / migrated.
234 247 *
235 248 * @param string $version The version that was installed.
236 249 */
237 - do_action( 'desktop_mode_files_schema_installed', DESKTOP_MODE_FILES_SCHEMA_VERSION );
250 + do_action( 'openstation_files_schema_installed', OPENSTATION_FILES_SCHEMA_VERSION );
238 251 }
239 252
240 253 /**
241 254 * Belt-and-suspenders verifier for the v2 trash columns. Reads
@@ -244,11 +257,11 @@
244 257 * `ALTER` only fires when the column is not already there.
245 258 *
246 259 * @internal
247 260 */
248 -function desktop_mode_files_ensure_trash_columns() {
261 +function openstation_files_ensure_trash_columns() {
249 262 global $wpdb;
250 - $tables = desktop_mode_files_table_names();
263 + $tables = openstation_files_table_names();
251 264
252 265 // Two-worker race protection: between the INFORMATION_SCHEMA
253 266 // check and the ALTER, a concurrent worker (cron + admin-init,
254 267 // REST + heartbeat) can run the same check, see the column
@@ -261,12 +274,12 @@
261 274 $ensure = static function ( $table, $column, $definition ) use ( $wpdb ) {
262 275 $col_exists = static function () use ( $wpdb, $table, $column ) {
263 276 return (int) $wpdb->get_var(
264 277 $wpdb->prepare(
265 - "SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS
278 + 'SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS
266 279 WHERE TABLE_SCHEMA = DATABASE()
267 280 AND TABLE_NAME = %s
268 - AND COLUMN_NAME = %s",
281 + AND COLUMN_NAME = %s',
269 282 $table,
270 283 $column
271 284 )
272 285 );
@@ -286,18 +299,18 @@
286 299 $wpdb->query( "ALTER TABLE `{$table}` ADD COLUMN `{$column}` {$definition}" );
287 300 }
288 301 };
289 302
290 - $ensure( $tables['placements'], 'trashed_at_ms', 'BIGINT UNSIGNED NULL' );
291 - $ensure( $tables['placements'], 'trashed_by', 'BIGINT UNSIGNED NULL' );
303 + $ensure( $tables['placements'], 'trashed_at_ms', 'BIGINT UNSIGNED NULL' );
304 + $ensure( $tables['placements'], 'trashed_by', 'BIGINT UNSIGNED NULL' );
292 305 $ensure( $tables['placements'], 'trashed_via_folder', 'BIGINT UNSIGNED NULL' );
293 306 // v6: ancestry snapshot — JSON capturing every folder in the
294 307 // parent chain at trash time so a restore can resurrect the
295 308 // chain even when a folder was hard-deleted in the meantime.
296 - $ensure( $tables['placements'], 'trashed_meta', 'LONGTEXT NULL' );
297 - $ensure( $tables['folders'], 'trashed_at_ms', 'BIGINT UNSIGNED NULL' );
298 - $ensure( $tables['folders'], 'trashed_by', 'BIGINT UNSIGNED NULL' );
299 - $ensure( $tables['folders'], 'trashed_meta', 'LONGTEXT NULL' );
309 + $ensure( $tables['placements'], 'trashed_meta', 'LONGTEXT NULL' );
310 + $ensure( $tables['folders'], 'trashed_at_ms', 'BIGINT UNSIGNED NULL' );
311 + $ensure( $tables['folders'], 'trashed_by', 'BIGINT UNSIGNED NULL' );
312 + $ensure( $tables['folders'], 'trashed_meta', 'LONGTEXT NULL' );
300 313 }
301 314
302 315 /**
303 316 * Collapse duplicate `(owner_id, parent_id, file_type, file_ref)`
@@ -308,16 +321,16 @@
308 321 * covers EVERY file type, so duplicates of any type within the
309 322 * same (owner, parent) are disallowed at the DB level; if legacy
310 323 * duplicates of another type exist, the (error-suppressed)
311 324 * `ADD UNIQUE` in
312 - * `desktop_mode_files_ensure_unique_placement_index()` will fail
325 + * `openstation_files_ensure_unique_placement_index()` will fail
313 326 * and leave the index absent until those rows are cleaned up.
314 327 *
315 328 * @internal
316 329 */
317 -function desktop_mode_files_dedupe_placements() {
330 +function openstation_files_dedupe_placements() {
318 331 global $wpdb;
319 - $tables = desktop_mode_files_table_names();
332 + $tables = openstation_files_table_names();
320 333 $tbl = $tables['placements'];
321 334
322 335 // Once the unique index exists, MySQL prevents duplicate
323 336 // inserts at the DB level — dedupe is a no-op and the
@@ -325,12 +338,12 @@
325 338 // call. Skip in that case so the cost is paid exactly once,
326 339 // during the v4 → v5 migration.
327 340 $has_unique = (int) $wpdb->get_var(
328 341 $wpdb->prepare(
329 - "SELECT COUNT(*) FROM INFORMATION_SCHEMA.STATISTICS
342 + 'SELECT COUNT(*) FROM INFORMATION_SCHEMA.STATISTICS
330 343 WHERE TABLE_SCHEMA = DATABASE()
331 344 AND TABLE_NAME = %s
332 - AND INDEX_NAME = %s",
345 + AND INDEX_NAME = %s',
333 346 $tbl,
334 347 'placement_unique'
335 348 )
336 349 );
@@ -366,19 +379,19 @@
366 379 * InnoDB index-key limit on `utf8mb4`.
367 380 *
368 381 * @internal
369 382 */
370 -function desktop_mode_files_ensure_unique_placement_index() {
383 +function openstation_files_ensure_unique_placement_index() {
371 384 global $wpdb;
372 - $tables = desktop_mode_files_table_names();
385 + $tables = openstation_files_table_names();
373 386 $tbl = $tables['placements'];
374 387
375 388 $exists = (int) $wpdb->get_var(
376 389 $wpdb->prepare(
377 - "SELECT COUNT(*) FROM INFORMATION_SCHEMA.STATISTICS
390 + 'SELECT COUNT(*) FROM INFORMATION_SCHEMA.STATISTICS
378 391 WHERE TABLE_SCHEMA = DATABASE()
379 392 AND TABLE_NAME = %s
380 - AND INDEX_NAME = %s",
393 + AND INDEX_NAME = %s',
381 394 $tbl,
382 395 'placement_unique'
383 396 )
384 397 );
@@ -401,9 +414,9 @@
401 414 /**
402 415 * Add the v10 `updated_by` column to the placements table.
403 416 *
404 417 * Tracks which user last mutated the row (created, moved, restored).
405 - * Used by `desktop_mode_files_check_if_match()` so the If-Match 409
418 + * Used by `openstation_files_check_if_match()` so the If-Match 409
406 419 * conflict toast attributes the change to the SESSION that won the
407 420 * race rather than to the row's static owner — critical when a
408 421 * writer recipient of a shared folder rearranges placements and
409 422 * another viewer hits a stale `If-Match`.
@@ -412,18 +425,18 @@
412 425 * to `owner_id` when this column is NULL, matching the old behavior.
413 426 *
414 427 * @internal
415 428 */
416 -function desktop_mode_files_ensure_updated_by_column() {
429 +function openstation_files_ensure_updated_by_column() {
417 430 global $wpdb;
418 - $tables = desktop_mode_files_table_names();
431 + $tables = openstation_files_table_names();
419 432 $tbl = $tables['placements'];
420 433 $exists = (int) $wpdb->get_var(
421 434 $wpdb->prepare(
422 - "SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS
435 + 'SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS
423 436 WHERE TABLE_SCHEMA = DATABASE()
424 437 AND TABLE_NAME = %s
425 - AND COLUMN_NAME = %s",
438 + AND COLUMN_NAME = %s',
426 439 $tbl,
427 440 'updated_by'
428 441 )
429 442 );
@@ -441,9 +454,9 @@
441 454
442 455 /**
443 456 * Rename `placements.user_id` to `placements.owner_id` and the
444 457 * matching `user_parent` index to `owner_parent`. The two
445 - * desktop-mode-owned tables historically used different names for
458 + * os-owned tables historically used different names for
446 459 * the same "row's owner" concept — folders carried `owner_id` from
447 460 * day one, placements carried `user_id`. v11 unifies them so SQL
448 461 * and PHP read identically across the two tables.
449 462 *
@@ -451,9 +464,9 @@
451 464 * dbDelta runs after this and creates the table with the new
452 465 * column name directly) and when the column is already renamed.
453 466 *
454 467 * Must run BEFORE `dbDelta( $placements_sql )` in
455 - * `desktop_mode_files_install_schema()` — dbDelta does NOT rename
468 + * `openstation_files_install_schema()` — dbDelta does NOT rename
456 469 * columns, so against a v≤10 table whose definition says
457 470 * `owner_id` it would ADD a new `owner_id` column and leave the
458 471 * stale `user_id` in place. Running the CHANGE COLUMN first puts
459 472 * the table in the desired shape so dbDelta sees no diff.
@@ -465,11 +478,11 @@
465 478 * so healthy v11 installs see a cheap no-op on the retry.
466 479 *
467 480 * @internal
468 481 */
469 -function desktop_mode_files_rename_user_id_to_owner_id() {
482 +function openstation_files_rename_user_id_to_owner_id() {
470 483 global $wpdb;
471 - $tables = desktop_mode_files_table_names();
484 + $tables = openstation_files_table_names();
472 485 $tbl = $tables['placements'];
473 486
474 487 // Fresh install — the table doesn't exist yet; dbDelta creates
475 488 // it with `owner_id` directly. Nothing to migrate.
@@ -474,10 +487,10 @@
474 487 // Fresh install — the table doesn't exist yet; dbDelta creates
475 488 // it with `owner_id` directly. Nothing to migrate.
476 489 $table_exists = (int) $wpdb->get_var(
477 490 $wpdb->prepare(
478 - "SELECT COUNT(*) FROM INFORMATION_SCHEMA.TABLES
479 - WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = %s",
491 + 'SELECT COUNT(*) FROM INFORMATION_SCHEMA.TABLES
492 + WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = %s',
480 493 $tbl
481 494 )
482 495 );
483 496 if ( 0 === $table_exists ) {
@@ -538,19 +551,19 @@
538 551 * pattern and `CREATE TABLE IF NOT EXISTS` the row explicitly.
539 552 *
540 553 * @internal
541 554 */
542 -function desktop_mode_files_ensure_shares_table() {
555 +function openstation_files_ensure_shares_table() {
543 556 global $wpdb;
544 - $tables = desktop_mode_files_table_names();
557 + $tables = openstation_files_table_names();
545 558 $charset_collate = $wpdb->get_charset_collate();
546 559 $tbl = $tables['shares'];
547 560
548 561 $exists = (int) $wpdb->get_var(
549 562 $wpdb->prepare(
550 - "SELECT COUNT(*) FROM INFORMATION_SCHEMA.TABLES
563 + 'SELECT COUNT(*) FROM INFORMATION_SCHEMA.TABLES
551 564 WHERE TABLE_SCHEMA = DATABASE()
552 - AND TABLE_NAME = %s",
565 + AND TABLE_NAME = %s',
553 566 $tbl
554 567 )
555 568 );
556 569 if ( 0 === $exists ) {
@@ -577,12 +590,12 @@
577 590 // Existing table — make sure `target_type` is there for
578 591 // installs that ran a pre-target_type build of v8.
579 592 $has_col = (int) $wpdb->get_var(
580 593 $wpdb->prepare(
581 - "SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS
594 + 'SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS
582 595 WHERE TABLE_SCHEMA = DATABASE()
583 596 AND TABLE_NAME = %s
584 - AND COLUMN_NAME = %s",
597 + AND COLUMN_NAME = %s',
585 598 $tbl,
586 599 'target_type'
587 600 )
588 601 );
@@ -602,19 +615,19 @@
602 615 * Belt-and-suspenders verifier for the decisions table.
603 616 *
604 617 * @internal
605 618 */
606 -function desktop_mode_files_ensure_decisions_table() {
619 +function openstation_files_ensure_decisions_table() {
607 620 global $wpdb;
608 - $tables = desktop_mode_files_table_names();
621 + $tables = openstation_files_table_names();
609 622 $charset_collate = $wpdb->get_charset_collate();
610 623 $tbl = $tables['decisions'];
611 624
612 625 $exists = (int) $wpdb->get_var(
613 626 $wpdb->prepare(
614 - "SELECT COUNT(*) FROM INFORMATION_SCHEMA.TABLES
627 + 'SELECT COUNT(*) FROM INFORMATION_SCHEMA.TABLES
615 628 WHERE TABLE_SCHEMA = DATABASE()
616 - AND TABLE_NAME = %s",
629 + AND TABLE_NAME = %s',
617 630 $tbl
618 631 )
619 632 );
620 633 if ( 0 === $exists ) {
@@ -638,23 +651,23 @@
638 651 * Lazy migrator — runs on `admin_init` when the stored schema
639 652 * version doesn't match the constant. Idempotent: `dbDelta`
640 653 * itself is a no-op when the table already matches.
641 654 */
642 -function desktop_mode_files_maybe_install_schema() {
643 - $installed = get_option( DESKTOP_MODE_FILES_SCHEMA_OPTION, '' );
644 - if ( $installed === DESKTOP_MODE_FILES_SCHEMA_VERSION ) {
655 +function openstation_files_maybe_install_schema() {
656 + $installed = get_option( OPENSTATION_FILES_SCHEMA_OPTION, '' );
657 + if ( OPENSTATION_FILES_SCHEMA_VERSION === $installed ) {
645 658 return;
646 659 }
647 - desktop_mode_files_install_schema();
660 + openstation_files_install_schema();
648 661 }
649 -add_action( 'admin_init', 'desktop_mode_files_maybe_install_schema' );
662 +add_action( 'admin_init', 'openstation_files_maybe_install_schema' );
650 663 // REST + front-end requests never fire `admin_init` — without these
651 664 // hooks a session that hits a REST endpoint before any admin page
652 665 // load would query the placements / folders tables before the v2
653 666 // trash columns exist, throwing wpdb errors and blanking the desktop.
654 -add_action( 'rest_api_init', 'desktop_mode_files_maybe_install_schema' );
655 -add_action( 'init', 'desktop_mode_files_maybe_install_schema', 1 );
656 -register_activation_hook( DESKTOP_MODE_FILE, 'desktop_mode_files_install_schema' );
667 +add_action( 'rest_api_init', 'openstation_files_maybe_install_schema' );
668 +add_action( 'init', 'openstation_files_maybe_install_schema', 1 );
669 +register_activation_hook( OPENSTATION_FILE, 'openstation_files_install_schema' );
657 670
658 671 /**
659 672 * Current epoch-ms timestamp. Centralized so the store and the
660 673 * tombstone writer stay in lock-step.
@@ -660,7 +673,7 @@
660 673 * tombstone writer stay in lock-step.
661 674 *
662 675 * @return int
663 676 */
664 -function desktop_mode_files_now_ms() {
677 +function openstation_files_now_ms() {
665 678 return (int) round( microtime( true ) * 1000 );
666 679 }