| @@ -1,31 +1,27 @@ | ||
| 1 | 1 | <?php |
| 2 | 2 | /** |
| 3 | - * OpenStation — My WordPress: window + pinned icon registration. | |
| 3 | + * OpenStation — My WordPress: the explorer's identity + shared helpers. | |
| 4 | 4 | * |
| 5 | - * Native window with id `desktop-mode-my-wordpress`, opened from a | |
| 6 | - * pinned desktop icon that always sits in the top-left of the grid | |
| 7 | - * (`pinned: true`, `position: -1`). The bundle renders a two-pane | |
| 8 | - * file-explorer UI with breadcrumb navigation: root shows Posts, | |
| 9 | - * Pages, Users, and Media folder tiles, and clicking one drills into | |
| 10 | - * an infinite-scroll list of entities with a per-kind preview pane. | |
| 5 | + * The app people open is `apps/my-wordpress/` — WP Explorer, rebuilt | |
| 6 | + * on the App Framework. This module keeps what the port shares: the | |
| 7 | + * name and the folder-mark icon (the app reads both from the helpers | |
| 8 | + * here), the capability gate, and the inert entities compatibility | |
| 9 | + * surface (see the note at the end of the file). | |
| 11 | 10 | * |
| 12 | 11 | * **The app is called WP Explorer; the module is called my-wordpress.** |
| 13 | - * The display name is the only thing that changed — the window id, the | |
| 14 | - * icon id, the script and style handles, the function prefix and every | |
| 15 | - * `openstation_my_wordpress_*` filter are the names plugins and stored | |
| 16 | - * desktop layouts already reference, so they stay put. See the frozen- | |
| 17 | - * values rule in AGENTS.md; renaming any of them here would orphan a | |
| 18 | - * user's pinned tile rather than move it. | |
| 12 | + * The function prefix and every `openstation_my_wordpress_*` filter | |
| 13 | + * are names plugins already reference — the frozen-values rule in | |
| 14 | + * AGENTS.md keeps them put. | |
| 19 | 15 | * |
| 20 | - * Filterable surface (mirrors the recycle-bin / posts-window modules): | |
| 16 | + * Filterable surface: | |
| 21 | 17 | * |
| 22 | - * - `openstation_my_wordpress_window_args` | |
| 23 | - * - `openstation_my_wordpress_icon_args` | |
| 24 | 18 | * - `openstation_my_wordpress_user_can_use` |
| 25 | - * - `openstation_my_wordpress_entities` | |
| 26 | - * - `openstation_my_wordpress_template_html` | |
| 19 | + * - `openstation_my_wordpress_entities` (inert — see the end note) | |
| 27 | 20 | * |
| 21 | + * (`openstation_my_wordpress_icon_args`, `_window_args` and | |
| 22 | + * `_template_html` went with the legacy window they configured.) | |
| 23 | + * | |
| 28 | 24 | * @package OpenStation |
| 29 | 25 | */ |
| 30 | 26 | |
| 31 | 27 | defined( 'ABSPATH' ) || exit; |
| @@ -95,8 +91,37 @@ | ||
| 95 | 91 | * |
| 96 | 92 | * Mirrors the recycle-bin gate — anyone who can edit posts can |
| 97 | 93 | * browse posts and pages. |
| 98 | 94 | * |
| 95 | + * This is the module's one capability gate, and it is a **server-side | |
| 96 | + * authorization** gate, not just a visibility one. It decides: | |
| 97 | + * | |
| 98 | + * - The non-REST post-type bridge: whether | |
| 99 | + * `desktop-mode/v1/post-type/<slug>` routes register at all | |
| 100 | + * (`rest-post-type.php`). | |
| 101 | + * - The per-comment dossier route | |
| 102 | + * `desktop-mode/v1/comment-stats/<id>` (`comment-stats.php`). | |
| 103 | + * - The user and term dossier routes | |
| 104 | + * `desktop-mode/v1/user-stats/<id>`, | |
| 105 | + * `desktop-mode/v1/user-footprint/<id>` and | |
| 106 | + * `desktop-mode/v1/term-stats/<taxonomy>/<id>` (`user-stats.php`, | |
| 107 | + * `user-footprint.php`, `term-stats.php`), which then scope their | |
| 108 | + * payloads to what the caller may read. | |
| 109 | + * - Whether the WooCommerce integration's boot config ships, so the | |
| 110 | + * client can reach the order / customer / product surfaces at all | |
| 111 | + * — those routes still enforce their own Woo capabilities on top | |
| 112 | + * (`integrations/woocommerce.php`). | |
| 113 | + * - Whether preview-action scripts registered by plugins are | |
| 114 | + * enqueued (`preview-actions.php`). | |
| 115 | + * - Whether Station Home offers the "WP Explorer" quick action | |
| 116 | + * (`apps/station-home/parts/snapshot.php`). | |
| 117 | + * | |
| 118 | + * It does **not** gate the app's own window or pinned launcher: WP | |
| 119 | + * Explorer is an App Framework app and declares | |
| 120 | + * `->capabilities( 'edit_posts' )` itself (`apps/my-wordpress/`). | |
| 121 | + * Returning `true` here opens the surfaces above without opening the | |
| 122 | + * window; to move the window too, filter the app's manifest. | |
| 123 | + * | |
| 99 | 124 | * @return bool |
| 100 | 125 | */ |
| 101 | 126 | function openstation_my_wordpress_user_can_use() { |
| 102 | 127 | $can = current_user_can( 'edit_posts' ); |
| @@ -101,10 +126,10 @@ | ||
| 101 | 126 | function openstation_my_wordpress_user_can_use() { |
| 102 | 127 | $can = current_user_can( 'edit_posts' ); |
| 103 | 128 | |
| 104 | 129 | /** |
| 105 | - * Filter whether the current user can see the My WordPress | |
| 106 | - * pinned icon and window. | |
| 130 | + * Filter whether the current user can reach the My WordPress | |
| 131 | + * module's surfaces — its REST routes included. | |
| 107 | 132 | * |
| 108 | 133 | * @param bool $can Default: edit_posts capability. |
| 109 | 134 | */ |
| 110 | 135 | return (bool) apply_filters( 'openstation_my_wordpress_user_can_use', $can ); |
| @@ -199,161 +224,22 @@ | ||
| 199 | 224 | $filtered = apply_filters( 'openstation_my_wordpress_entities', $entities ); |
| 200 | 225 | return is_array( $filtered ) ? array_values( $filtered ) : $entities; |
| 201 | 226 | } |
| 202 | 227 | |
| 203 | -/** | |
| 204 | - * Render the My WordPress window's static template body. The bundle | |
| 205 | - * mounts its UI into `[data-os-my-wordpress-root]`. | |
| 206 | - */ | |
| 207 | -function openstation_my_wordpress_render_template() { | |
| 208 | - ob_start(); | |
| 209 | - ?> | |
| 210 | - <div class="desktop-mode-my-wordpress" data-os-my-wordpress-root> | |
| 211 | - <header data-os-my-wordpress-breadcrumbs></header> | |
| 212 | - <div class="os-my-wordpress__body" data-os-my-wordpress-body> | |
| 213 | - <div class="os-my-wordpress__loading" data-os-my-wordpress-loading hidden> | |
| 214 | - <os-spinner></os-spinner> | |
| 215 | - </div> | |
| 216 | - </div> | |
| 217 | - <div class="os-folder-status-bar" data-os-my-wordpress-status></div> | |
| 218 | - </div> | |
| 219 | - <?php | |
| 220 | - $html = (string) ob_get_clean(); | |
| 221 | - | |
| 222 | - /** | |
| 223 | - * Filter the My WordPress window's template HTML. | |
| 224 | - * | |
| 225 | - * @param string $html Default template HTML. | |
| 226 | - */ | |
| 227 | - $filtered = (string) apply_filters( 'openstation_my_wordpress_template_html', $html ); | |
| 228 | - | |
| 229 | - $allowed_html = function_exists( 'openstation_native_window_allowed_html' ) | |
| 230 | - ? openstation_native_window_allowed_html() | |
| 231 | - : wp_kses_allowed_html( 'post' ); | |
| 232 | - | |
| 233 | - echo wp_kses( $filtered, $allowed_html ); | |
| 234 | -} | |
| 235 | - | |
| 236 | -/** | |
| 237 | - * Register the native window + the pinned wallpaper icon on `init`, | |
| 238 | - * priority 99 — after `components.php` boots the registry, and late | |
| 239 | - * enough that every plugin's `register_post_type()` call has already | |
| 240 | - * run. The entity list is frozen into the window config here and only | |
| 241 | - * emitted later on `admin_enqueue_scripts`, so a CPT registered after | |
| 242 | - * this point would never reach the bundle. `previewActions` is the | |
| 243 | - * exception: it is re-collected at emit time via | |
| 244 | - * `openstation_my_wordpress_refresh_window_config()` below, so the | |
| 245 | - * value snapshotted here is only the fallback. | |
| 246 | - */ | |
| 247 | -function openstation_my_wordpress_register_window() { | |
| 248 | - if ( ! openstation_my_wordpress_user_can_use() ) { | |
| 249 | - return; | |
| 250 | - } | |
| 251 | - | |
| 252 | - $site_title = openstation_site_title(); | |
| 253 | - $app_title = openstation_my_wordpress_app_title(); | |
| 254 | - $icon_uri = 'data:image/svg+xml;base64,' . base64_encode( openstation_my_wordpress_icon_svg() ); | |
| 255 | - | |
| 256 | - $entities = openstation_my_wordpress_entities(); | |
| 257 | - | |
| 258 | - $window_args = array( | |
| 259 | - 'title' => $app_title, | |
| 260 | - 'icon' => $icon_uri, | |
| 261 | - 'template' => 'openstation_my_wordpress_render_template', | |
| 262 | - 'script' => 'desktop-mode-my-wordpress', | |
| 263 | - // `styles` (companion), not `style`: loads on first open | |
| 264 | - // rather than at every shell boot. Declared FIRST so the | |
| 265 | - // WooCommerce integration's sheet — appended to this array by | |
| 266 | - // `openstation_my_wordpress_woo_window_args()` — lands after | |
| 267 | - // it in the head and keeps winning their equal-specificity | |
| 268 | - // overrides by source order. | |
| 269 | - 'styles' => array( 'desktop-mode-my-wordpress' ), | |
| 270 | - 'width' => 960, | |
| 271 | - 'height' => 640, | |
| 272 | - 'min_width' => 640, | |
| 273 | - 'min_height' => 420, | |
| 274 | - 'placement' => 'none', | |
| 275 | - 'config' => array( | |
| 276 | - 'restRoot' => esc_url_raw( rest_url() ), | |
| 277 | - 'restNonce' => wp_create_nonce( 'wp_rest' ), | |
| 278 | - 'editPostUrlBase' => esc_url_raw( admin_url( 'post.php' ) ), | |
| 279 | - 'editUserUrlBase' => esc_url_raw( admin_url( 'user-edit.php' ) ), | |
| 280 | - 'siteName' => $site_title, | |
| 281 | - 'entities' => $entities, | |
| 282 | - 'groups' => function_exists( 'openstation_my_wordpress_collect_groups' ) | |
| 283 | - ? openstation_my_wordpress_collect_groups( $entities ) | |
| 284 | - : array(), | |
| 285 | - 'perPage' => 24, | |
| 286 | - 'mediaPerPage' => 48, | |
| 287 | - 'previewActions' => function_exists( 'openstation_my_wordpress_collect_preview_actions' ) | |
| 288 | - ? openstation_my_wordpress_collect_preview_actions() | |
| 289 | - : array(), | |
| 290 | - ), | |
| 291 | - ); | |
| 292 | - | |
| 293 | - /** | |
| 294 | - * Filter the args used to register the My WordPress native window. | |
| 295 | - * | |
| 296 | - * @param array $window_args Args passed to `openstation_register_window()`. | |
| 297 | - */ | |
| 298 | - $window_args = (array) apply_filters( 'openstation_my_wordpress_window_args', $window_args ); | |
| 299 | - | |
| 300 | - $registered = openstation_register_window( 'desktop-mode-my-wordpress', $window_args ); | |
| 301 | - if ( is_wp_error( $registered ) ) { | |
| 302 | - // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log | |
| 303 | - error_log( '[openstation] My WordPress window registration failed: ' . $registered->get_error_message() ); | |
| 304 | - return; | |
| 305 | - } | |
| 306 | - | |
| 307 | - $icon_args = array( | |
| 308 | - 'title' => $app_title, | |
| 309 | - 'icon' => $icon_uri, | |
| 310 | - 'window' => 'desktop-mode-my-wordpress', | |
| 311 | - 'pinned' => true, | |
| 312 | - 'position' => -1, | |
| 313 | - ); | |
| 314 | - | |
| 315 | - /** | |
| 316 | - * Filter the args used to register the My WordPress pinned icon. | |
| 317 | - * | |
| 318 | - * Removing `pinned` here lets the icon participate in normal | |
| 319 | - * sort order — useful for sites that want the shortcut to feel | |
| 320 | - * like any other plugin icon. | |
| 321 | - * | |
| 322 | - * @param array $icon_args Args passed to `openstation_register_icon()`. | |
| 323 | - */ | |
| 324 | - $icon_args = (array) apply_filters( 'openstation_my_wordpress_icon_args', $icon_args ); | |
| 325 | - | |
| 326 | - openstation_register_icon( 'desktop-mode-my-wordpress', $icon_args ); | |
| 327 | -} | |
| 328 | -add_action( 'init', 'openstation_my_wordpress_register_window', 99 ); | |
| 329 | - | |
| 330 | -/** | |
| 331 | - * Re-collect the preview-action descriptors when the window config is | |
| 332 | - * serialized for the browser, replacing the `init` 99 snapshot taken | |
| 333 | - * at registration time. | |
| 228 | +/* | |
| 229 | + * No window, no template, no launcher. The explorer people open is | |
| 230 | + * the `my-wordpress` APP (`apps/my-wordpress/`), which reclaimed the | |
| 231 | + * "WP Explorer" name and the folder mark from this module's helpers | |
| 232 | + * above. The legacy native window, its bundle and its pinned icon are | |
| 233 | + * gone; every deep surface it used to host moved with the port — the | |
| 234 | + * detail dossiers and the activity footprint render inside the app, | |
| 235 | + * "open this object" travels through the shared stores in | |
| 236 | + * `src/open-targets/explorer-open.ts` / `footprint-target.ts`, and | |
| 237 | + * the Recycle Bin trashes dropped rows by their payload's `restPath`. | |
| 334 | 238 | * |
| 335 | - * The action SCRIPTS were always collected late (`admin_enqueue_scripts` | |
| 336 | - * 40, `openstation_my_wordpress_enqueue_preview_action_scripts()`), so | |
| 337 | - * a plugin registering its `openstation_my_wordpress_preview_actions` | |
| 338 | - * callback after `init` 99 used to get its JS delivered with no | |
| 339 | - * descriptor to attach to — the button simply never rendered. Emitting | |
| 340 | - * both legs from the same late collection closes that trap: any | |
| 341 | - * registration during a normal bootstrap now ships both. | |
| 342 | - * | |
| 343 | - * The entity list deliberately stays frozen at `init` 99 — see the | |
| 344 | - * registration docblock above. | |
| 345 | - * | |
| 346 | - * @param array $config Window config blob about to be emitted. | |
| 347 | - * @param string $window_id Native window id. | |
| 348 | - * @return array Config with fresh `previewActions` for our window. | |
| 239 | + * `openstation_my_wordpress_entities()` and its filter remain as an | |
| 240 | + * inert compatibility surface: subscribers registered against them | |
| 241 | + * (WooCommerce's own among them) still run, and the pure helpers are | |
| 242 | + * still what the tests pin — but nothing consumes the list to build a | |
| 243 | + * window any more. The `openstation_my_wordpress_window_args` / | |
| 244 | + * `openstation_my_wordpress_template_html` filters no longer fire. | |
| 349 | 245 | */ |
| 350 | -function openstation_my_wordpress_refresh_window_config( $config, $window_id ) { | |
| 351 | - if ( 'desktop-mode-my-wordpress' !== $window_id || ! is_array( $config ) ) { | |
| 352 | - return $config; | |
| 353 | - } | |
| 354 | - if ( function_exists( 'openstation_my_wordpress_collect_preview_actions' ) ) { | |
| 355 | - $config['previewActions'] = openstation_my_wordpress_collect_preview_actions(); | |
| 356 | - } | |
| 357 | - return $config; | |
| 358 | -} | |
| 359 | -add_filter( 'openstation_native_window_config', 'openstation_my_wordpress_refresh_window_config', 10, 2 ); | |