PluginProbe
OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin / 0.9.2
OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin v0.9.2
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 0.9.2, at includes/pages-window/window.php

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