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 / apps / os-settings / os-settings.os.php

os-settings.os.php in OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin 1.1.10, at apps/os-settings/os-settings.os.php

211 lines 7.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * OpenStation Preferences — the settings window, as an OpenStation app.
4 *
5 * THE Preferences window: the App Framework rebuild replaced the
6 * legacy JS-registered native window and its lazy panel bundle, and
7 * claims its id — `desktop-mode-os-settings` is a frozen identifier
8 * (see AGENTS.md), so saved sessions, the System tile's flyout row,
9 * `wp.os.openOsSettings()` and the default-window marker keep working
10 * unchanged. The window is this file; the body is `os-settings.os.ts`,
11 * a client view painting every page from the shell's settings store
12 * through the public `wp.os` API.
13 *
14 * The settings are NOT this app's state — they are per-user meta the
15 * shell applies before its first paint (`includes/os-settings.php`,
16 * `src/settings/`). This app's state is the page; its server surface
17 * is the handful of writes that are site truth rather than a
18 * preference, plus `data()` for the facts that can change mid-session.
19 *
20 * (Header kept short on purpose: Plugin Check's direct-access scan
21 * reads only the first 50 raw lines, and the guard below must land
22 * inside that window.)
23 *
24 * @package OpenStation
25 */
26
27 namespace OpenStation\Apps\OsSettings;
28
29 use OpenStation\App;
30 use OpenStation\App\Os;
31 use OpenStation\App\State;
32
33 // Direct access, unless a standalone host is booting on bare PHP.
34 if ( ! defined( 'ABSPATH' ) ) {
35 defined( 'OPENSTATION_STANDALONE' ) || exit;
36 }
37
38 /** The window id — a frozen identifier, see the file header. */
39 const ID = 'desktop-mode-os-settings';
40
41 /**
42 * The gear the Preferences window wears — the same eight-toothed
43 * annulus the System tile's flyout draws (`src/ui/gear-icon.ts`).
44 * Hand-drawn rather than `dashicons-admin-generic`, which is also the
45 * fallback every registry hands a plugin that registered without art:
46 * the one window that should be findable at a glance would have
47 * looked exactly like the tiles nobody bothered to draw. Drawn in
48 * `currentColor`, like every other piece of shell art.
49 *
50 * @return string SVG markup.
51 */
52 function gear_svg() {
53 $teeth = '';
54 for ( $i = 0; $i < 8; $i++ ) {
55 $teeth .= sprintf(
56 '<rect x="28" y="5" width="8" height="12" rx="2" fill="currentColor" transform="rotate(%d 32 32)"/>',
57 45 * $i
58 );
59 }
60 return '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">' . $teeth
61 . '<circle cx="32" cy="32" r="15.5" fill="none" stroke="currentColor" stroke-width="9"/></svg>';
62 }
63
64 /**
65 * Refuse an action that changes SITE truth to anyone who may not.
66 * The app itself is open to every shell user; these actions are not.
67 *
68 * @param Os $os Host handle.
69 * @return void
70 * @throws \RuntimeException When the acting user lacks `manage_options`.
71 */
72 function require_admin( Os $os ) {
73 if ( ! $os->can( 'manage_options' ) ) {
74 throw new \RuntimeException( esc_html__( 'You are not allowed to change site-wide options.', 'desktop-mode' ) );
75 }
76 }
77
78 /**
79 * The server facts the client view paints from — the ones that can
80 * change while the window is open. The `focus` lifecycle action
81 * recomputes them whenever the window regains focus, which is how the
82 * AI toggle un-gates after the user connects a provider elsewhere.
83 *
84 * @param State $state Unused — nothing here depends on the page.
85 * @param Os $os Host handle.
86 * @return array<string,mixed>
87 */
88 function data( State $state, Os $os ) {
89 $admin = $os->can( 'manage_options' );
90 return array(
91 'isAdmin' => $admin,
92 'canUpload' => $os->can( 'upload_files' ),
93 'canManageDesktopThemes' => function_exists( 'openstation_desktop_theme_upload_capability' )
94 && $os->can( openstation_desktop_theme_upload_capability() ),
95 'extendedOptions' => $admin && function_exists( 'openstation_get_extended_options' )
96 ? openstation_get_extended_options()
97 : null,
98 'aiAssistant' => function_exists( 'openstation_ai_assistant_config' )
99 ? openstation_ai_assistant_config( $os->auth->user_id() )
100 : null,
101 );
102 }
103
104 /**
105 * Extended Options: merged over the stored set, then a menu refresh —
106 * every option gates a server-side registration (`games` decides
107 * whether the games module loads at all), and the shell only learns
108 * what the server registers from a fresh payload.
109 *
110 * @param State $state Unused.
111 * @param Os $os Host handle.
112 * @param array<string,mixed> $args `options` (array of bools).
113 * @return void
114 */
115 function extended_action( State $state, Os $os, array $args ) {
116 require_admin( $os );
117 if ( function_exists( 'openstation_save_extended_options' ) ) {
118 openstation_save_extended_options( isset( $args['options'] ) && is_array( $args['options'] ) ? $args['options'] : array() );
119 }
120 $os->refresh_menu();
121 }
122
123 /**
124 * "Delete folder sharing data" — drops every shares table, the same
125 * destructive cleanup the files REST route performs.
126 *
127 * @param State $state Unused.
128 * @param Os $os Host handle.
129 * @return void
130 */
131 function purge_shares_action( State $state, Os $os ) {
132 require_admin( $os );
133 if ( ! function_exists( 'openstation_files_rest_purge_sharing_tables' ) ) {
134 $os->toast( __( 'Files REST endpoint is not available.', 'desktop-mode' ) );
135 return;
136 }
137 $response = openstation_files_rest_purge_sharing_tables();
138 $dropped = $response instanceof \WP_REST_Response ? (array) $response->get_data()['dropped'] : array();
139 $os->toast(
140 sprintf(
141 /* translators: %d: number of tables dropped. */
142 __( 'Folder sharing data deleted. (%d tables)', 'desktop-mode' ),
143 count( $dropped )
144 )
145 );
146 }
147
148 return App::define( ID )
149 // Not translated: the product's own name, as the legacy window had it.
150 ->title( 'OpenStation Preferences' )
151 ->icon( gear_svg() )
152 ->size( 820, 720 )
153 ->min_size( 560, 480 )
154 // No launcher of its own: the System tile's flyout row opens it
155 // and answers for it on the rail (`NavItem.answersFor`), exactly
156 // as before. `wp.os.openOsSettings()` is the portable opener.
157 ->placement( 'none' )
158 // The settings are the user's own, so the network admin's shell
159 // offers the window too; its site options there are the main site's.
160 ->admin( 'any' )
161 ->capabilities( 'read' )
162 // `data()` is a handful of capability checks and options, so it
163 // ships with the window and the pages paint the moment the window
164 // opens — as the legacy panel did — instead of behind a spinner for
165 // the length of the `mount` round trip.
166 ->prefetch()
167 // The page is the whole declared state; the settings live in the
168 // shell's store and reach the view through `wp.os.getOsSettings()`.
169 ->state( array( 'tab' => 'appearance' ) )
170 // `wp.os.openOsSettings( { tabId } )` lands on the page it named.
171 ->mount(
172 static function ( State $state, Os $os ) {
173 $tab = sanitize_key( (string) $os->param( 'tab', '' ) );
174 if ( '' !== $tab ) {
175 $state->set( 'tab', $tab );
176 }
177 }
178 )
179 ->action( 'extended', __NAMESPACE__ . '\extended_action' )
180 ->action(
181 'reset-intros',
182 static function ( State $state, Os $os ) {
183 if ( function_exists( 'openstation_clear_seen_intros' ) ) {
184 openstation_clear_seen_intros( $os->auth->user_id() );
185 }
186 }
187 )
188 ->action( 'purge-shares', __NAMESPACE__ . '\purge_shares_action' )
189 // Regaining focus re-probes the server facts: nothing to run, the
190 // recomputed `data()` is the point.
191 ->action( 'focus', static function () {} )
192 ->data( __NAMESPACE__ . '\data' )
193 // The static facts, shipped once with the window config.
194 ->config(
195 array(
196 'mediaUrl' => esc_url_raw( rest_url( 'wp/v2/media' ) ),
197 'desktopThemesUrl' => esc_url_raw( rest_url( 'desktop-mode/v1/desktop-themes' ) ),
198 'aboutFeedUrl' => esc_url_raw(
199 add_query_arg(
200 array(
201 'action' => 'openstation_about_feed',
202 'nonce' => wp_create_nonce( 'openstation_about_feed' ),
203 ),
204 admin_url( 'admin-ajax.php' )
205 )
206 ),
207 'pluginUrl' => esc_url_raw( untrailingslashit( OPENSTATION_URL ) ),
208 'pluginVersion' => OPENSTATION_VERSION,
209 )
210 );
211