PluginProbe
OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin / 1.1.8
OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin v1.1.8
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 0.8.6 All 33 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.8, at apps/os-settings/os-settings.os.php

235 lines 8.6 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 'commentsAi' => $admin && function_exists( 'openstation_comments_ai_is_enabled' )
99 ? array(
100 'enabled' => openstation_comments_ai_is_enabled(),
101 'providerConfigured' => openstation_comments_ai_provider_configured(),
102 )
103 : null,
104 'aiAssistant' => function_exists( 'openstation_ai_assistant_config' )
105 ? openstation_ai_assistant_config( $os->auth->user_id() )
106 : null,
107 );
108 }
109
110 /**
111 * The comments-AI toggle: a site option, the same write the
112 * `POST desktop-mode/v1/comments/ai-settings` route makes.
113 *
114 * @param State $state Unused.
115 * @param Os $os Host handle.
116 * @param array<string,mixed> $args `enabled` (bool).
117 * @return void
118 */
119 function comments_ai_action( State $state, Os $os, array $args ) {
120 require_admin( $os );
121 if ( ! defined( 'OPENSTATION_COMMENTS_AI_OPTION' ) ) {
122 return;
123 }
124 $enabled = ! empty( $args['enabled'] );
125 update_option( OPENSTATION_COMMENTS_AI_OPTION, $enabled, false );
126 /** This action is documented in apps/comments/parts/ai-moderation.php */
127 do_action( 'openstation_comments_ai_toggled', $enabled );
128 }
129
130 /**
131 * Extended Options: merged over the stored set, then a menu refresh —
132 * every option gates a server-side registration (`games` decides
133 * whether the games module loads at all), and the shell only learns
134 * what the server registers from a fresh payload.
135 *
136 * @param State $state Unused.
137 * @param Os $os Host handle.
138 * @param array<string,mixed> $args `options` (array of bools).
139 * @return void
140 */
141 function extended_action( State $state, Os $os, array $args ) {
142 require_admin( $os );
143 if ( function_exists( 'openstation_save_extended_options' ) ) {
144 openstation_save_extended_options( isset( $args['options'] ) && is_array( $args['options'] ) ? $args['options'] : array() );
145 }
146 $os->refresh_menu();
147 }
148
149 /**
150 * "Delete folder sharing data" — drops every shares table, the same
151 * destructive cleanup the files REST route performs.
152 *
153 * @param State $state Unused.
154 * @param Os $os Host handle.
155 * @return void
156 */
157 function purge_shares_action( State $state, Os $os ) {
158 require_admin( $os );
159 if ( ! function_exists( 'openstation_files_rest_purge_sharing_tables' ) ) {
160 $os->toast( __( 'Files REST endpoint is not available.', 'desktop-mode' ) );
161 return;
162 }
163 $response = openstation_files_rest_purge_sharing_tables();
164 $dropped = $response instanceof \WP_REST_Response ? (array) $response->get_data()['dropped'] : array();
165 $os->toast(
166 sprintf(
167 /* translators: %d: number of tables dropped. */
168 __( 'Folder sharing data deleted. (%d tables)', 'desktop-mode' ),
169 count( $dropped )
170 )
171 );
172 }
173
174 return App::define( ID )
175 // Not translated: the product's own name, as the legacy window had it.
176 ->title( 'OpenStation Preferences' )
177 ->icon( gear_svg() )
178 ->size( 820, 720 )
179 ->min_size( 560, 480 )
180 // No launcher of its own: the System tile's flyout row opens it
181 // and answers for it on the rail (`NavItem.answersFor`), exactly
182 // as before. `wp.os.openOsSettings()` is the portable opener.
183 ->placement( 'none' )
184 ->capabilities( 'read' )
185 // `data()` is a handful of capability checks and options, so it
186 // ships with the window and the pages paint the moment the window
187 // opens — as the legacy panel did — instead of behind a spinner for
188 // the length of the `mount` round trip.
189 ->prefetch()
190 // The page is the whole declared state; the settings live in the
191 // shell's store and reach the view through `wp.os.getOsSettings()`.
192 ->state( array( 'tab' => 'appearance' ) )
193 // `wp.os.openOsSettings( { tabId } )` lands on the page it named.
194 ->mount(
195 static function ( State $state, Os $os ) {
196 $tab = sanitize_key( (string) $os->param( 'tab', '' ) );
197 if ( '' !== $tab ) {
198 $state->set( 'tab', $tab );
199 }
200 }
201 )
202 ->action( 'extended', __NAMESPACE__ . '\extended_action' )
203 ->action( 'comments-ai', __NAMESPACE__ . '\comments_ai_action' )
204 ->action(
205 'reset-intros',
206 static function ( State $state, Os $os ) {
207 if ( function_exists( 'openstation_clear_seen_intros' ) ) {
208 openstation_clear_seen_intros( $os->auth->user_id() );
209 }
210 }
211 )
212 ->action( 'purge-shares', __NAMESPACE__ . '\purge_shares_action' )
213 // Regaining focus re-probes the server facts: nothing to run, the
214 // recomputed `data()` is the point.
215 ->action( 'focus', static function () {} )
216 ->data( __NAMESPACE__ . '\data' )
217 // The static facts, shipped once with the window config.
218 ->config(
219 array(
220 'mediaUrl' => esc_url_raw( rest_url( 'wp/v2/media' ) ),
221 'desktopThemesUrl' => esc_url_raw( rest_url( 'desktop-mode/v1/desktop-themes' ) ),
222 'aboutFeedUrl' => esc_url_raw(
223 add_query_arg(
224 array(
225 'action' => 'openstation_about_feed',
226 'nonce' => wp_create_nonce( 'openstation_about_feed' ),
227 ),
228 admin_url( 'admin-ajax.php' )
229 )
230 ),
231 'pluginUrl' => esc_url_raw( untrailingslashit( OPENSTATION_URL ) ),
232 'pluginVersion' => OPENSTATION_VERSION,
233 )
234 );
235