PluginProbe
OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin / 1.1.1
OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin v1.1.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 / pages-window / window.php

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

287 lines 10.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * OpenStation — Native Pages Window: registration + template.
4 *
5 * Mirrors the structure of the Posts window (`includes/posts-window/window.php`),
6 * adapted for the `page` post type:
7 * - No taxonomy tabs (pages have no Categories/Tags surface in core).
8 * - Hierarchical: surfaces a Parent column and `orderby=menu_order` default.
9 * - Same lock badge via the `openstation_lock` REST field.
10 *
11 * @package OpenStation
12 */
13
14 defined( 'ABSPATH' ) || exit;
15
16 /**
17 * Echoes the native Pages window's template body.
18 *
19 * Same toolbar/table/pager structure as the Posts window, minus the
20 * `<os-tabs>` taxonomy tabs. The `data-os-posts-*` hooks
21 * are the contract the JS bundle relies on — keep them intact (or
22 * rename via the filter) when customizing the layout.
23 */
24 function openstation_pages_window_render_template() {
25 ob_start();
26 ?>
27 <div class="desktop-mode-posts" data-os-posts-root>
28 <div class="os-posts__panel">
29 <header class="os-posts__toolbar" data-os-posts-toolbar>
30 <div class="os-posts__toolbar-left">
31 <os-segmented data-os-posts-status value=""></os-segmented>
32 <os-text-field
33 data-os-posts-search
34 placeholder="<?php esc_attr_e( 'Search pages…', 'desktop-mode' ); ?>"
35 ></os-text-field>
36 </div>
37 <div class="os-posts__toolbar-right" data-os-posts-bulk hidden>
38 <span class="os-posts__count" data-os-posts-count></span>
39 <span class="os-posts__bulk-actions" data-os-posts-bulk-actions></span>
40 </div>
41 <div class="os-posts__toolbar-trailing">
42 <span class="os-posts__toolbar-extras" data-os-posts-toolbar-extras></span>
43 <os-button variant="ghost" data-os-posts-refresh title="<?php esc_attr_e( 'Refresh', 'desktop-mode' ); ?>">
44 <span class="dashicons dashicons-update" aria-hidden="true"></span>
45 </os-button>
46 <os-button variant="primary" data-os-posts-new>
47 <span class="dashicons dashicons-plus" aria-hidden="true"></span>
48 <?php esc_html_e( 'Add New', 'desktop-mode' ); ?>
49 </os-button>
50 </div>
51 </header>
52 <div class="os-posts__body" data-os-posts-body>
53 <os-table
54 data-os-posts-table
55 selectable="multi"
56 sticky-header
57 sticky-columns="1"
58 hover
59 striped
60 bordered
61 loading
62 >
63 <div slot="empty" class="os-posts__empty">
64 <span class="dashicons dashicons-admin-page" aria-hidden="true"></span>
65 <p><?php esc_html_e( 'No pages found.', 'desktop-mode' ); ?></p>
66 <p class="os-posts__empty-hint">
67 <?php esc_html_e( 'Try a different search or change the status filter.', 'desktop-mode' ); ?>
68 </p>
69 </div>
70 </os-table>
71 </div>
72 <footer class="os-posts__pager" data-os-posts-pager>
73 <div class="os-posts__pager-meta">
74 <span data-os-posts-page-indicator></span>
75 </div>
76 <div class="os-posts__pager-nav">
77 <os-button variant="ghost" data-os-posts-prev disabled>
78 <span class="dashicons dashicons-arrow-left-alt2" aria-hidden="true"></span>
79 <?php esc_html_e( 'Previous', 'desktop-mode' ); ?>
80 </os-button>
81 <os-button variant="ghost" data-os-posts-next disabled>
82 <?php esc_html_e( 'Next', 'desktop-mode' ); ?>
83 <span class="dashicons dashicons-arrow-right-alt2" aria-hidden="true"></span>
84 </os-button>
85 <label class="os-posts__pager-perpage">
86 <?php esc_html_e( 'Per page', 'desktop-mode' ); ?>
87 <select data-os-posts-per-page>
88 <option value="10">10</option>
89 <option value="20" selected>20</option>
90 <option value="50">50</option>
91 <option value="100">100</option>
92 </select>
93 </label>
94 </div>
95 </footer>
96 </div>
97 </div>
98 <?php
99 $html = (string) ob_get_clean();
100
101 /**
102 * Filter the native Pages window's template HTML.
103 *
104 * Keep the `data-os-posts-*` hooks intact so the JS
105 * render callback can find its mount points.
106 *
107 * @param string $html Default template HTML.
108 */
109 $filtered = (string) apply_filters( 'openstation_pages_window_template_html', $html );
110
111 if ( function_exists( 'openstation_kses_native_window_template' ) ) {
112 echo openstation_kses_native_window_template( $filtered ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- helper kses-escapes.
113 } else {
114 echo wp_kses( $filtered, wp_kses_allowed_html( 'post' ) );
115 }
116 }
117
118 /**
119 * Register the native Pages window on `init` (priority 20).
120 */
121 function openstation_pages_window_register_window() {
122 if ( ! openstation_pages_window_user_can_register() ) {
123 return;
124 }
125
126 $window_args = array(
127 'title' => __( 'Pages', 'desktop-mode' ),
128 'icon' => 'dashicons-admin-page',
129 'template' => 'openstation_pages_window_render_template',
130 // Both Posts and Pages share a single bundle — `index.ts`
131 // registers `desktop-mode-posts` AND `desktop-mode-pages` from
132 // the same module. Loading `os-posts-window` for the
133 // Pages window reuses the already-cached script + style.
134 'script' => 'os-posts-window',
135 'style' => 'os-posts-window',
136 'width' => 1100,
137 'height' => 720,
138 'min_width' => 720,
139 'min_height' => 480,
140 'placement' => 'none',
141 'config' => array(
142 // `mode` is the JS-side discriminator the shared bundle reads
143 // to gate Pages-only behavior (parent column, no taxonomy
144 // tabs). The Posts config omits this key, so the bundle
145 // defaults to `'posts'` for backwards compat.
146 'mode' => 'pages',
147 'restRoot' => esc_url_raw( rest_url() ),
148 'restNonce' => wp_create_nonce( 'wp_rest' ),
149 // Use the `/wp/v2/pages` collection rather than `/wp/v2/posts`
150 // so core's hierarchical column shapers (parent, menu_order)
151 // are surfaced without an explicit `post_type` query arg.
152 'postsUrl' => esc_url_raw( rest_url( 'wp/v2/pages' ) ),
153 'editPostUrlBase' => esc_url_raw( admin_url( 'post.php' ) ),
154 'newPostUrl' => esc_url_raw( add_query_arg( 'post_type', 'page', admin_url( 'post-new.php' ) ) ),
155 'usersUrl' => esc_url_raw( rest_url( 'wp/v2/users' ) ),
156 'currentUserId' => (int) get_current_user_id(),
157 'defaultPerPage' => 20,
158 'queryArgs' => openstation_pages_window_default_query_args(),
159 // Reading-page assignments — surfaced so the title cell can
160 // paint "Front page" / "Posts page" badges on matching rows
161 // (one of the most-asked Pages-list usability gaps in the
162 // WordPress trac + community forums). `0` when unset.
163 'frontPageId' => (int) get_option( 'page_on_front', 0 ),
164 'postsPageId' => (int) get_option( 'page_for_posts', 0 ),
165 // Page-template label map: `{ slug: human label }` for the
166 // templates the active theme registers. Falls back to the
167 // raw slug when a theme registers a template the table
168 // hasn't seen — better to show "page-fullwidth.php" than to
169 // hide which template is in use.
170 'pageTemplates' => openstation_pages_window_template_labels(),
171 ),
172 );
173
174 /**
175 * Filter the args used to register the native Pages window.
176 *
177 * @param array $window_args Args passed to `openstation_register_window()`.
178 */
179 $window_args = (array) apply_filters( 'openstation_pages_window_args', $window_args );
180
181 $registered = openstation_register_window( 'desktop-mode-pages', $window_args );
182 if ( is_wp_error( $registered ) ) {
183 // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
184 error_log( '[openstation] Native Pages window registration failed: ' . $registered->get_error_message() );
185 }
186 }
187 add_action( 'init', 'openstation_pages_window_register_window', 20 );
188
189 /**
190 * Default REST query args for the Pages window.
191 *
192 * @return array
193 */
194 function openstation_pages_window_default_query_args() {
195 $args = array(
196 // Pages are usually shallow + ordered by menu_order; embed the
197 // author + featured media for the table cells.
198 '_embed' => 'author,wp:featuredmedia',
199 // `slug`, `template`, `link` and the custom `openstation_comment_count`
200 // field are pulled in for the new Slug / Template / Comments
201 // columns and the public-URL "View" quick-action. Missing them
202 // from the whitelist costs nothing on the wire (REST will skip
203 // them) but does silently break the column — keep them here.
204 '_fields' =>
205 'id,title,status,date,date_gmt,modified,modified_gmt,author,parent,menu_order,slug,link,template,comment_status,excerpt,openstation_lock,openstation_comment_count,_links,_embedded',
206 'orderby' => 'menu_order',
207 'order' => 'asc',
208 );
209
210 /**
211 * Filter the default outbound REST query args for the Pages window.
212 *
213 * @param array $args Default args.
214 */
215 return (array) apply_filters( 'openstation_pages_window_query_args', $args );
216 }
217
218 /**
219 * Build the `{ slug: label }` map for the active theme's registered
220 * page templates. Used by the Pages window's Template column to
221 * paint friendly labels instead of raw filenames.
222 *
223 * The "default" template (assigned when `page.template` is `''`) is
224 * keyed under the empty string for parity with what core returns in
225 * `/wp/v2/pages` responses.
226 *
227 * @return array<string,string>
228 */
229 function openstation_pages_window_template_labels() {
230 $labels = array(
231 '' => __( 'Default template', 'desktop-mode' ),
232 );
233 if ( function_exists( 'wp_get_theme' ) ) {
234 $theme = wp_get_theme();
235 if ( $theme && method_exists( $theme, 'get_page_templates' ) ) {
236 $registered = (array) $theme->get_page_templates( null, 'page' );
237 foreach ( $registered as $slug => $label ) {
238 $labels[ (string) $slug ] = (string) $label;
239 }
240 }
241 }
242
243 /**
244 * Filter the page-template label map handed to the Pages window.
245 *
246 * @param array<string,string> $labels Slug → human label.
247 */
248 return (array) apply_filters( 'openstation_pages_window_template_labels', $labels );
249 }
250
251 /**
252 * Register the `openstation_comment_count` REST field on `page`.
253 *
254 * The default `/wp/v2/pages` response doesn't include a comment
255 * count — surfacing one alongside the row keeps parity with the
256 * classic Pages list (where the count is a column) without forcing
257 * the table to embed `_embed=replies`, which is heavy and N+1.
258 *
259 * Registered on `page` only for now; other CPTs can opt in by
260 * cloning this `register_rest_field` call. Posts already track
261 * comments via the classic admin and can be wired the same way
262 * if/when the Posts window grows the column.
263 */
264 function openstation_pages_window_register_comment_count_field() {
265 register_rest_field(
266 'page',
267 'openstation_comment_count',
268 array(
269 'get_callback' => static function ( $row ) {
270 $id = isset( $row['id'] ) ? (int) $row['id'] : 0;
271 if ( $id <= 0 ) {
272 return 0;
273 }
274 return (int) get_comments_number( $id );
275 },
276 'schema' => array(
277 'description' => __( 'Total non-trashed comments on this page.', 'desktop-mode' ),
278 'type' => 'integer',
279 'context' => array( 'view', 'embed' ),
280 'readonly' => true,
281 ),
282 )
283 );
284 }
285 add_action( 'rest_api_init', 'openstation_pages_window_register_comment_count_field' );
286
287