PluginProbe
OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin / 1.0.1
OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin v1.0.1
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 / includes / my-wordpress / window.php

window.php in OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin 1.0.1, at includes/my-wordpress/window.php

326 lines 13.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * OpenStation — My WordPress: window + pinned icon registration.
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.
11 *
12 * **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.
19 *
20 * Filterable surface (mirrors the recycle-bin / posts-window modules):
21 *
22 * - `openstation_my_wordpress_window_args`
23 * - `openstation_my_wordpress_icon_args`
24 * - `openstation_my_wordpress_user_can_use`
25 * - `openstation_my_wordpress_entities`
26 * - `openstation_my_wordpress_template_html`
27 *
28 * @package OpenStation
29 */
30
31 defined( 'ABSPATH' ) || exit;
32
33 /**
34 * Display name of the app, for the window title and the pinned icon.
35 *
36 * A file explorer, named after what it explores. The window used to
37 * carry the site title instead, on the reasoning that the desktop holds
38 * objects rather than a mention of the software you are already standing
39 * in — but a site title is what the *root folder* is called, not what
40 * the app is called, and the breadcrumb still says it. The two are no
41 * longer the same string.
42 *
43 * @return string
44 */
45 function openstation_my_wordpress_app_title() {
46 return __( 'WP Explorer', 'desktop-mode' );
47 }
48
49 /**
50 * The app's icon: a folder wearing the WordPress mark.
51 *
52 * The window used to wear `dashicons-wordpress`, the double-ringed
53 * wp.org mark, which said "WordPress" and nothing about what the app
54 * does — every other window here is WordPress too. A folder says
55 * "browse", and the single-ringed mark inside it says whose files.
56 *
57 * Drawn in `currentColor` so both painters mask it: the dock masks
58 * image icons to the rail's own glyph colour so a plugin's brand
59 * colours cannot break the monochrome rail, and `renderIcon()` masks it
60 * to the title bar's text colour. A literal fill would silently turn it
61 * back into a background image in one and a solid blob in the other.
62 *
63 * The folder is outlined rather than filled so the mark inside it stays
64 * a mark: on a filled folder, a same-colour mark is invisible. Hand-
65 * placed at 64×64, the established canvas for the custom icons here,
66 * and held to two shapes because it renders as small as 20px in the
67 * dock. The mark's own path is the upstream single-ringed logo, a
68 * filled disc with the W knocked out by the nonzero fill rule.
69 *
70 * On the sizing: the front panel runs `y 19.1 → 52.1` on the
71 * centreline, so at stroke 3 the clear height inside it is 30 units.
72 * The mark takes 20 of those at scale 1, centred on 35.6, which leaves
73 * five units above and below. It used to be scaled 1.15× for 23 units
74 * and barely three units of clearance, less than one stroke width, and
75 * a disc that close to the panel edge reads as crammed in rather than
76 * placed. Horizontal clearance was never the constraint: the panel is
77 * 50 units wide against the mark's 20.
78 *
79 * @return string Raw `<svg>` markup.
80 */
81 function openstation_my_wordpress_icon_svg() {
82 return '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">'
83 // The folder: back tab, notch, front panel, all one outline.
84 . '<path d="M11 13h12.2a3 3 0 0 1 2.4 1.2l2.8 3.7a3 3 0 0 0 2.4 1.2H53a4 4 0 0 1 4 4v25a4 4 0 0 1-4 4H11a4 4 0 0 1-4-4V17a4 4 0 0 1 4-4z"'
85 . ' fill="none" stroke="currentColor" stroke-width="3" stroke-linejoin="round"/>'
86 // The WordPress mark, centred on the front panel at r=10.
87 . '<g transform="translate(22 25.6) scale(1)">'
88 . '<path fill="currentColor" d="M20 10c0-5.51-4.49-10-10-10C4.48 0 0 4.49 0 10c0 5.52 4.48 10 10 10 5.51 0 10-4.48 10-10zM7.78 15.37L4.37 6.22c.55-.02 1.17-.08 1.17-.08.5-.06.44-1.13-.06-1.11 0 0-1.45.11-2.37.11-.18 0-.37 0-.58-.01C4.12 2.69 6.87 1.11 10 1.11c2.33 0 4.45.87 6.05 2.34-.68-.11-1.65.39-1.65 1.58 0 .74.45 1.36.9 2.1.35.61.55 1.36.55 2.46 0 1.49-1.4 5-1.4 5l-3.03-8.37c.54-.02.82-.17.82-.17.5-.05.44-1.25-.06-1.22 0 0-1.44.12-2.38.12-.87 0-2.33-.12-2.33-.12-.5-.03-.56 1.2-.06 1.22l.92.08 1.26 3.41zM17.41 10c.24-.64.74-1.87.43-4.25.7 1.29 1.05 2.71 1.05 4.25 0 3.29-1.73 6.24-4.4 7.78.97-2.59 1.94-5.2 2.92-7.78zM6.1 18.09C3.12 16.65 1.11 13.53 1.11 10c0-1.3.23-2.48.72-3.59C3.25 10.3 4.67 14.2 6.1 18.09zm4.03-6.63l2.58 6.98c-.86.29-1.76.45-2.71.45-.79 0-1.57-.11-2.29-.33.81-2.38 1.62-4.74 2.42-7.1z"/>'
89 . '</g>'
90 . '</svg>';
91 }
92
93 /**
94 * Whether the current user should see My WordPress.
95 *
96 * Mirrors the recycle-bin gate — anyone who can edit posts can
97 * browse posts and pages.
98 *
99 * @return bool
100 */
101 function openstation_my_wordpress_user_can_use() {
102 $can = current_user_can( 'edit_posts' );
103
104 /**
105 * Filter whether the current user can see the My WordPress
106 * pinned icon and window.
107 *
108 * @param bool $can Default: edit_posts capability.
109 */
110 return (bool) apply_filters( 'openstation_my_wordpress_user_can_use', $can );
111 }
112
113 /**
114 * Build the entity list shipped to the bundle. Posts, Pages,
115 * Users, and Media. Future phases add
116 * Comments, Tags, Categories, Themes, and Plugins.
117 *
118 * The optional `kind` field tells the bundle how to render entries
119 * of this entity: `'post'` (default) renders the standard
120 * title/excerpt/featured-image tile and the rendered-HTML preview;
121 * `'user'` renders an avatar + display-name tile and routes to the
122 * user dossier preview; `'media'` renders a thumbnail-grid tile and
123 * routes to the media preview pane. Plugins extending the entity
124 * list with a post-shaped collection can omit the field; user- and
125 * media-shaped collections must set `'user'` / `'media'`.
126 * The optional `post_type` field specifies the WP post-type
127 * slug used for `os.<slug>.changed` cross-window broadcasts.
128 *
129 * @return array[] Each entry is `array( 'id', 'label', 'icon',
130 * 'restPath', 'kind', 'post_type' )`. `restPath` is appended to
131 * the `restRoot` config to derive the list URL.
132 */
133 function openstation_my_wordpress_entities() {
134 $entities = array(
135 array(
136 'id' => 'posts',
137 'label' => __( 'Posts', 'desktop-mode' ),
138 'icon' => 'dashicons-admin-post',
139 'restPath' => 'wp/v2/posts',
140 'kind' => 'post',
141 'post_type' => 'post',
142 ),
143 array(
144 'id' => 'pages',
145 'label' => __( 'Pages', 'desktop-mode' ),
146 'icon' => 'dashicons-admin-page',
147 'restPath' => 'wp/v2/pages',
148 'kind' => 'post',
149 'post_type' => 'page',
150 ),
151 array(
152 'id' => 'users',
153 'label' => __( 'Users', 'desktop-mode' ),
154 'icon' => 'dashicons-admin-users',
155 'restPath' => 'wp/v2/users',
156 'kind' => 'user',
157 ),
158 array(
159 'id' => 'media',
160 'label' => __( 'Media', 'desktop-mode' ),
161 'icon' => 'dashicons-admin-media',
162 'restPath' => 'wp/v2/media',
163 'kind' => 'media',
164 'post_type' => 'attachment',
165 ),
166 );
167
168 /**
169 * Filter the list of entity types shown inside the My WordPress
170 * window. Each entry must declare `id`, `label`, `icon`, and
171 * `restPath`. Returning a reordered or extended array shows up
172 * in the bundle on the next render.
173 *
174 * **Status: Experimental** — the entity descriptor shape may
175 * gain fields as new entity kinds land (Comments, Tags,
176 * Categories, Themes, Plugins). Stable id/label/icon/restPath
177 * fields will continue to work; new optional fields will not
178 * break existing consumers. The `kind` field is optional and
179 * defaults to `'post'` for back-compat.
180 *
181 * Optional fields:
182 * - `kind` — render strategy (`'post'` default, `'user'`, `'media'`).
183 * - `post_type` — WP post-type slug for cross-window broadcast topic `os.<slug>.changed`.
184 * - `thumbnails` — set false to keep the section icon on every tile
185 * instead of the entity's featured image.
186 * - `group` — folder id this section nests under at the root
187 * (null / omitted renders it loose at the root).
188 * - `groupLabel` — folder label. `groupIcon`, `groupOrder` follow.
189 *
190 * @param array[] $entities Default entities.
191 */
192 $filtered = apply_filters( 'openstation_my_wordpress_entities', $entities );
193 return is_array( $filtered ) ? array_values( $filtered ) : $entities;
194 }
195
196 /**
197 * Render the My WordPress window's static template body. The bundle
198 * mounts its UI into `[data-os-my-wordpress-root]`.
199 */
200 function openstation_my_wordpress_render_template() {
201 ob_start();
202 ?>
203 <div class="desktop-mode-my-wordpress" data-os-my-wordpress-root>
204 <header data-os-my-wordpress-breadcrumbs></header>
205 <div class="os-my-wordpress__body" data-os-my-wordpress-body>
206 <div class="os-my-wordpress__loading" data-os-my-wordpress-loading hidden>
207 <os-spinner></os-spinner>
208 </div>
209 </div>
210 <div class="os-folder-status-bar" data-os-my-wordpress-status></div>
211 </div>
212 <?php
213 $html = (string) ob_get_clean();
214
215 /**
216 * Filter the My WordPress window's template HTML.
217 *
218 * @param string $html Default template HTML.
219 */
220 $filtered = (string) apply_filters( 'openstation_my_wordpress_template_html', $html );
221
222 $allowed_html = function_exists( 'openstation_native_window_allowed_html' )
223 ? openstation_native_window_allowed_html()
224 : wp_kses_allowed_html( 'post' );
225
226 echo wp_kses( $filtered, $allowed_html );
227 }
228
229 /**
230 * Register the native window + the pinned wallpaper icon on `init`,
231 * priority 99 — after `components.php` boots the registry, and late
232 * enough that every plugin's `register_post_type()` call has already
233 * run. The entity list is frozen into the window config here and only
234 * emitted later on `admin_enqueue_scripts`, so a CPT registered after
235 * this point would never reach the bundle.
236 */
237 function openstation_my_wordpress_register_window() {
238 if ( ! openstation_my_wordpress_user_can_use() ) {
239 return;
240 }
241
242 $site_title = openstation_site_title();
243 $app_title = openstation_my_wordpress_app_title();
244 $icon_uri = 'data:image/svg+xml;base64,' . base64_encode( openstation_my_wordpress_icon_svg() );
245
246 $entities = openstation_my_wordpress_entities();
247
248 $window_args = array(
249 'title' => $app_title,
250 'icon' => $icon_uri,
251 'template' => 'openstation_my_wordpress_render_template',
252 'script' => 'desktop-mode-my-wordpress',
253 'style' => 'desktop-mode-my-wordpress',
254 'width' => 960,
255 'height' => 640,
256 'min_width' => 640,
257 'min_height' => 420,
258 'placement' => 'none',
259 'config' => array(
260 'restRoot' => esc_url_raw( rest_url() ),
261 'restNonce' => wp_create_nonce( 'wp_rest' ),
262 'editPostUrlBase' => esc_url_raw( admin_url( 'post.php' ) ),
263 'editUserUrlBase' => esc_url_raw( admin_url( 'user-edit.php' ) ),
264 'siteName' => $site_title,
265 'entities' => $entities,
266 'groups' => function_exists( 'openstation_my_wordpress_collect_groups' )
267 ? openstation_my_wordpress_collect_groups( $entities )
268 : array(),
269 'perPage' => 24,
270 'mediaPerPage' => 48,
271 'previewActions' => function_exists( 'openstation_my_wordpress_collect_preview_actions' )
272 ? openstation_my_wordpress_collect_preview_actions()
273 : array(),
274 ),
275 );
276
277 /**
278 * Filter the args used to register the My WordPress native window.
279 *
280 * @param array $window_args Args passed to `openstation_register_window()`.
281 */
282 $window_args = (array) apply_filters( 'openstation_my_wordpress_window_args', $window_args );
283
284 $registered = openstation_register_window( 'desktop-mode-my-wordpress', $window_args );
285 if ( is_wp_error( $registered ) ) {
286 // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
287 error_log( '[openstation] My WordPress window registration failed: ' . $registered->get_error_message() );
288 return;
289 }
290
291 $icon_args = array(
292 'title' => $app_title,
293 'icon' => $icon_uri,
294 'window' => 'desktop-mode-my-wordpress',
295 'pinned' => true,
296 'position' => -1,
297 );
298
299 /**
300 * Filter the args used to register the My WordPress pinned icon.
301 *
302 * Removing `pinned` here lets the icon participate in normal
303 * sort order — useful for sites that want the shortcut to feel
304 * like any other plugin icon.
305 *
306 * @param array $icon_args Args passed to `openstation_register_icon()`.
307 */
308 $icon_args = (array) apply_filters( 'openstation_my_wordpress_icon_args', $icon_args );
309
310 openstation_register_icon( 'desktop-mode-my-wordpress', $icon_args );
311 }
312 add_action( 'init', 'openstation_my_wordpress_register_window', 99 );
313
314 /**
315 * Enqueue the bundle's CSS in admin context. The script is lazy-
316 * loaded by the native-window sync and so does not need an
317 * `admin_enqueue_scripts` call.
318 */
319 function openstation_my_wordpress_enqueue_styles() {
320 if ( ! openstation_my_wordpress_user_can_use() ) {
321 return;
322 }
323 wp_enqueue_style( 'desktop-mode-my-wordpress' );
324 }
325 add_action( 'admin_enqueue_scripts', 'openstation_my_wordpress_enqueue_styles', 30 );
326