| @@ -1,34 +1,41 @@ | ||
| 1 | 1 | <?php |
| 2 | 2 | /** |
| 3 | - * Desktop Mode — Desktop-theme REST routes. | |
| 3 | + * OpenStation — Desktop-theme REST routes. | |
| 4 | 4 | * |
| 5 | + * GET /desktop-mode/v1/desktop-themes full entries | |
| 5 | 6 | * POST /desktop-mode/v1/desktop-themes multipart `file` |
| 6 | 7 | * DELETE /desktop-mode/v1/desktop-themes/<slug> |
| 7 | 8 | * |
| 8 | - * There is deliberately no GET: the library rides the shell payload | |
| 9 | - * (`serverDesktopThemes`), so a separate read route would be a | |
| 10 | - * second source of truth to keep in sync for no gain. | |
| 9 | + * The GET exists for the boot-payload diet, and it is NOT a second | |
| 10 | + * source of truth: it returns exactly | |
| 11 | + * `openstation_build_desktop_themes_payload()` — the same builder, | |
| 12 | + * the same `openstation_desktop_themes` filter — with the FULL | |
| 13 | + * entries. The boot payload ships the library slimmed (no `cssText`, | |
| 14 | + * no `tokens`; `cssDeferred: true` marks the gap) because the active | |
| 15 | + * theme's stylesheet is server-delivered at boot and an inactive | |
| 16 | + * theme's ~20 KB of compiled CSS is only needed at the moment the | |
| 17 | + * user picks it — which is when the shell calls this route. | |
| 11 | 18 | * |
| 12 | - * @package WPDesktopMode | |
| 19 | + * @package OpenStation | |
| 13 | 20 | */ |
| 14 | 21 | |
| 15 | 22 | defined( 'ABSPATH' ) || exit; |
| 16 | 23 | |
| 17 | 24 | /** |
| 18 | - * Permission gate: the standard desktop-mode REST gate plus the | |
| 25 | + * Permission gate: the standard openstation REST gate plus the | |
| 19 | 26 | * theme-management capability. |
| 20 | 27 | * |
| 21 | 28 | * @return true|WP_Error |
| 22 | 29 | */ |
| 23 | -function desktop_mode_desktop_themes_rest_permission() { | |
| 24 | - $base = desktop_mode_rest_require_enabled(); | |
| 30 | +function openstation_desktop_themes_rest_permission() { | |
| 31 | + $base = openstation_rest_require_enabled(); | |
| 25 | 32 | if ( is_wp_error( $base ) ) { |
| 26 | 33 | return $base; |
| 27 | 34 | } |
| 28 | - if ( ! current_user_can( desktop_mode_desktop_theme_upload_capability() ) ) { | |
| 35 | + if ( ! current_user_can( openstation_desktop_theme_upload_capability() ) ) { | |
| 29 | 36 | return new WP_Error( |
| 30 | - 'desktop_mode_desktop_theme_cannot_manage', | |
| 37 | + 'openstation_desktop_theme_cannot_manage', | |
| 31 | 38 | __( 'You are not allowed to manage desktop themes.', 'desktop-mode' ), |
| 32 | 39 | array( 'status' => 403 ) |
| 33 | 40 | ); |
| 34 | 41 | } |
| @@ -37,17 +44,28 @@ | ||
| 37 | 44 | |
| 38 | 45 | /** |
| 39 | 46 | * Register the routes. |
| 40 | 47 | */ |
| 41 | -function desktop_mode_register_desktop_themes_rest_routes() { | |
| 48 | +function openstation_register_desktop_themes_rest_routes() { | |
| 42 | 49 | register_rest_route( |
| 43 | 50 | 'desktop-mode/v1', |
| 44 | 51 | '/desktop-themes', |
| 45 | 52 | array( |
| 46 | - // POST only — PHP populates `$_FILES` for real POSTs only. | |
| 47 | - 'methods' => WP_REST_Server::CREATABLE, | |
| 48 | - 'permission_callback' => 'desktop_mode_desktop_themes_rest_permission', | |
| 49 | - 'callback' => 'desktop_mode_rest_upload_desktop_theme', | |
| 53 | + array( | |
| 54 | + // Read gate is the shell's own, NOT the manage | |
| 55 | + // capability: the same full entries used to ride the | |
| 56 | + // boot payload to every desktop user, so the route | |
| 57 | + // exposes nothing the payload didn't. | |
| 58 | + 'methods' => WP_REST_Server::READABLE, | |
| 59 | + 'permission_callback' => 'openstation_rest_require_enabled', | |
| 60 | + 'callback' => 'openstation_rest_list_desktop_themes', | |
| 61 | + ), | |
| 62 | + array( | |
| 63 | + // POST only — PHP populates `$_FILES` for real POSTs only. | |
| 64 | + 'methods' => WP_REST_Server::CREATABLE, | |
| 65 | + 'permission_callback' => 'openstation_desktop_themes_rest_permission', | |
| 66 | + 'callback' => 'openstation_rest_upload_desktop_theme', | |
| 67 | + ), | |
| 50 | 68 | ) |
| 51 | 69 | ); |
| 52 | 70 | |
| 53 | 71 | register_rest_route( |
| @@ -54,10 +72,10 @@ | ||
| 54 | 72 | 'desktop-mode/v1', |
| 55 | 73 | '/desktop-themes/(?P<slug>[a-z0-9_-]+)', |
| 56 | 74 | array( |
| 57 | 75 | 'methods' => WP_REST_Server::DELETABLE, |
| 58 | - 'permission_callback' => 'desktop_mode_desktop_themes_rest_permission', | |
| 59 | - 'callback' => 'desktop_mode_rest_delete_desktop_theme', | |
| 76 | + 'permission_callback' => 'openstation_desktop_themes_rest_permission', | |
| 77 | + 'callback' => 'openstation_rest_delete_desktop_theme', | |
| 60 | 78 | 'args' => array( |
| 61 | 79 | 'slug' => array( |
| 62 | 80 | 'type' => 'string', |
| 63 | 81 | 'required' => true, |
| @@ -65,17 +83,36 @@ | ||
| 65 | 83 | ), |
| 66 | 84 | ) |
| 67 | 85 | ); |
| 68 | 86 | } |
| 69 | -add_action( 'rest_api_init', 'desktop_mode_register_desktop_themes_rest_routes' ); | |
| 87 | +add_action( 'rest_api_init', 'openstation_register_desktop_themes_rest_routes' ); | |
| 70 | 88 | |
| 71 | 89 | /** |
| 90 | + * GET /desktop-themes — the full theme library. | |
| 91 | + * | |
| 92 | + * The on-demand counterpart of the slimmed boot payload: same | |
| 93 | + * builder, same filter, full `cssText` / `tokens`. The shell calls | |
| 94 | + * it from `ensureFullDesktopThemes()` the first time a deferred | |
| 95 | + * entry's stylesheet is actually needed (the user picks a theme in | |
| 96 | + * Preferences → Themes). | |
| 97 | + * | |
| 98 | + * @return WP_REST_Response | |
| 99 | + */ | |
| 100 | +function openstation_rest_list_desktop_themes() { | |
| 101 | + return rest_ensure_response( | |
| 102 | + array( | |
| 103 | + 'themes' => openstation_build_desktop_themes_payload(), | |
| 104 | + ) | |
| 105 | + ); | |
| 106 | +} | |
| 107 | + | |
| 108 | +/** | |
| 72 | 109 | * POST /desktop-mode/v1/desktop-themes |
| 73 | 110 | * |
| 74 | 111 | * @param WP_REST_Request $request Request. |
| 75 | 112 | * @return WP_REST_Response|WP_Error The payload-shaped entry. |
| 76 | 113 | */ |
| 77 | -function desktop_mode_rest_upload_desktop_theme( WP_REST_Request $request ) { | |
| 114 | +function openstation_rest_upload_desktop_theme( WP_REST_Request $request ) { | |
| 78 | 115 | $files = $request->get_file_params(); |
| 79 | 116 | |
| 80 | 117 | // A body over `post_max_size` reaches PHP with $_POST and $_FILES |
| 81 | 118 | // both empty while CONTENT_LENGTH says bytes were sent. Answer a |
| @@ -84,15 +121,15 @@ | ||
| 84 | 121 | if ( empty( $files ) ) { |
| 85 | 122 | $content_length = isset( $_SERVER['CONTENT_LENGTH'] ) ? (int) $_SERVER['CONTENT_LENGTH'] : 0; |
| 86 | 123 | if ( $content_length > 0 ) { |
| 87 | 124 | return new WP_Error( |
| 88 | - 'desktop_mode_desktop_theme_too_large', | |
| 125 | + 'openstation_desktop_theme_too_large', | |
| 89 | 126 | __( 'That theme archive is larger than this server accepts.', 'desktop-mode' ), |
| 90 | 127 | array( 'status' => 413 ) |
| 91 | 128 | ); |
| 92 | 129 | } |
| 93 | 130 | return new WP_Error( |
| 94 | - 'desktop_mode_desktop_theme_no_file', | |
| 131 | + 'openstation_desktop_theme_no_file', | |
| 95 | 132 | __( 'No theme archive was uploaded.', 'desktop-mode' ), |
| 96 | 133 | array( 'status' => 400 ) |
| 97 | 134 | ); |
| 98 | 135 | } |
| @@ -97,9 +134,9 @@ | ||
| 97 | 134 | ); |
| 98 | 135 | } |
| 99 | 136 | if ( empty( $files['file'] ) || ! is_array( $files['file'] ) ) { |
| 100 | 137 | return new WP_Error( |
| 101 | - 'desktop_mode_desktop_theme_no_file', | |
| 138 | + 'openstation_desktop_theme_no_file', | |
| 102 | 139 | __( 'No theme archive was uploaded.', 'desktop-mode' ), |
| 103 | 140 | array( 'status' => 400 ) |
| 104 | 141 | ); |
| 105 | 142 | } |
| @@ -113,9 +150,9 @@ | ||
| 113 | 150 | $segments = explode( '.', strtolower( $name ) ); |
| 114 | 151 | $last = array_pop( $segments ); |
| 115 | 152 | if ( 'zip' !== $last ) { |
| 116 | 153 | return new WP_Error( |
| 117 | - 'desktop_mode_desktop_theme_not_zip', | |
| 154 | + 'openstation_desktop_theme_not_zip', | |
| 118 | 155 | __( 'A desktop theme must be uploaded as a .zip archive.', 'desktop-mode' ), |
| 119 | 156 | array( 'status' => 400 ) |
| 120 | 157 | ); |
| 121 | 158 | } |
| @@ -123,9 +160,9 @@ | ||
| 123 | 160 | array_shift( $segments ); // First segment is the base name. |
| 124 | 161 | foreach ( $segments as $segment ) { |
| 125 | 162 | if ( in_array( $segment, $denied, true ) ) { |
| 126 | 163 | return new WP_Error( |
| 127 | - 'desktop_mode_desktop_theme_not_zip', | |
| 164 | + 'openstation_desktop_theme_not_zip', | |
| 128 | 165 | __( 'That file name is not allowed.', 'desktop-mode' ), |
| 129 | 166 | array( 'status' => 400 ) |
| 130 | 167 | ); |
| 131 | 168 | } |
| @@ -133,9 +170,9 @@ | ||
| 133 | 170 | |
| 134 | 171 | $max = (int) wp_max_upload_size(); |
| 135 | 172 | if ( $max > 0 && isset( $file['size'] ) && (int) $file['size'] > $max ) { |
| 136 | 173 | return new WP_Error( |
| 137 | - 'desktop_mode_desktop_theme_too_large', | |
| 174 | + 'openstation_desktop_theme_too_large', | |
| 138 | 175 | sprintf( |
| 139 | 176 | /* translators: %s: formatted maximum file size. */ |
| 140 | 177 | __( 'That theme archive is larger than the allowed maximum of %s.', 'desktop-mode' ), |
| 141 | 178 | size_format( $max ) |
| @@ -146,23 +183,23 @@ | ||
| 146 | 183 | |
| 147 | 184 | $tmp = isset( $file['tmp_name'] ) ? (string) $file['tmp_name'] : ''; |
| 148 | 185 | if ( '' === $tmp || ! file_exists( $tmp ) ) { |
| 149 | 186 | return new WP_Error( |
| 150 | - 'desktop_mode_desktop_theme_no_file', | |
| 187 | + 'openstation_desktop_theme_no_file', | |
| 151 | 188 | __( 'The uploaded archive could not be read.', 'desktop-mode' ), |
| 152 | 189 | array( 'status' => 400 ) |
| 153 | 190 | ); |
| 154 | 191 | } |
| 155 | 192 | |
| 156 | - $entry = desktop_mode_desktop_theme_install_from_zip( $tmp ); | |
| 193 | + $entry = openstation_desktop_theme_install_from_zip( $tmp ); | |
| 157 | 194 | if ( is_wp_error( $entry ) ) { |
| 158 | 195 | return $entry; |
| 159 | 196 | } |
| 160 | 197 | |
| 161 | - $shaped = desktop_mode_shape_desktop_theme_payload_entry( $entry, 'upload' ); | |
| 198 | + $shaped = openstation_shape_desktop_theme_payload_entry( $entry, 'upload' ); | |
| 162 | 199 | if ( ! $shaped ) { |
| 163 | 200 | return new WP_Error( |
| 164 | - 'desktop_mode_desktop_theme_install_failed', | |
| 201 | + 'openstation_desktop_theme_install_failed', | |
| 165 | 202 | __( 'The theme installed but could not be described back to the shell.', 'desktop-mode' ), |
| 166 | 203 | array( 'status' => 500 ) |
| 167 | 204 | ); |
| 168 | 205 | } |
| @@ -168,9 +205,9 @@ | ||
| 168 | 205 | } |
| 169 | 206 | |
| 170 | 207 | // Wallpapers the theme brought with it. |
| 171 | 208 | // |
| 172 | - // `desktop_mode_register_desktop_theme_wallpapers()` already ran on | |
| 209 | + // `openstation_register_desktop_theme_wallpapers()` already ran on | |
| 173 | 210 | // `init` for THIS request — before the upload existed — so the new |
| 174 | 211 | // theme's wallpapers are not in the registry yet. Re-running it now |
| 175 | 212 | // picks them up (registration is idempotent: same ids, same store), |
| 176 | 213 | // and the shell applies the rebuilt list without a reload. Without |
| @@ -176,10 +213,10 @@ | ||
| 176 | 213 | // and the shell applies the rebuilt list without a reload. Without |
| 177 | 214 | // this the wallpapers only appeared on the next page load, which is |
| 178 | 215 | // exactly the kind of "it works after F5" seam this payload channel |
| 179 | 216 | // exists to remove. |
| 180 | - desktop_mode_register_desktop_theme_wallpapers(); | |
| 181 | - $shaped['serverWallpapers'] = desktop_mode_build_desktop_wallpapers_payload(); | |
| 217 | + openstation_register_desktop_theme_wallpapers(); | |
| 218 | + $shaped['serverWallpapers'] = openstation_build_desktop_wallpapers_payload(); | |
| 182 | 219 | |
| 183 | 220 | return rest_ensure_response( $shaped ); |
| 184 | 221 | } |
| 185 | 222 | |
| @@ -188,11 +225,11 @@ | ||
| 188 | 225 | * |
| 189 | 226 | * @param WP_REST_Request $request Request. |
| 190 | 227 | * @return WP_REST_Response|WP_Error |
| 191 | 228 | */ |
| 192 | -function desktop_mode_rest_delete_desktop_theme( WP_REST_Request $request ) { | |
| 229 | +function openstation_rest_delete_desktop_theme( WP_REST_Request $request ) { | |
| 193 | 230 | $slug = sanitize_key( (string) $request['slug'] ); |
| 194 | - $deleted = desktop_mode_desktop_theme_delete( $slug ); | |
| 231 | + $deleted = openstation_desktop_theme_delete( $slug ); | |
| 195 | 232 | if ( is_wp_error( $deleted ) ) { |
| 196 | 233 | return $deleted; |
| 197 | 234 | } |
| 198 | 235 | // The deleted theme's wallpapers were registered on `init`, into a |
| @@ -199,11 +236,11 @@ | ||
| 199 | 236 | // per-request static store we have no unregister API for. Filtering |
| 200 | 237 | // them out of the response is enough and avoids inventing one: the |
| 201 | 238 | // store dies with the request, and the next one never registers |
| 202 | 239 | // them because the theme is gone. |
| 203 | - $prefix = DESKTOP_MODE_DESKTOP_THEME_WALLPAPER_PREFIX . $slug . '/'; | |
| 240 | + $prefix = OPENSTATION_DESKTOP_THEME_WALLPAPER_PREFIX . $slug . '/'; | |
| 204 | 241 | $wallpapers = array(); |
| 205 | - foreach ( desktop_mode_build_desktop_wallpapers_payload() as $wallpaper ) { | |
| 242 | + foreach ( openstation_build_desktop_wallpapers_payload() as $wallpaper ) { | |
| 206 | 243 | $id = isset( $wallpaper['id'] ) ? (string) $wallpaper['id'] : ''; |
| 207 | 244 | if ( '' !== $id && 0 === strpos( $id, $prefix ) ) { |
| 208 | 245 | continue; |
| 209 | 246 | } |