PluginProbe
OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin / 1.1.5
OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin v1.1.5
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 / station-home / rest.php

rest.php in OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin 1.1.5, at includes/station-home/rest.php

422 lines 12.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * OpenStation — Station Home snapshot endpoint.
4 *
5 * @package OpenStation
6 */
7
8 defined( 'ABSPATH' ) || exit;
9
10 /**
11 * Register the current-user Station Home snapshot route.
12 */
13 function openstation_station_home_register_rest_routes() {
14 register_rest_route(
15 'desktop-mode/v1',
16 '/station-home',
17 array(
18 'methods' => 'GET',
19 'callback' => 'openstation_station_home_rest_snapshot',
20 'permission_callback' => 'openstation_rest_require_enabled',
21 )
22 );
23
24 register_rest_route(
25 'desktop-mode/v1',
26 '/station-home/cards',
27 array(
28 'methods' => 'POST',
29 'callback' => 'openstation_station_home_rest_update_card_preference',
30 'permission_callback' => 'openstation_rest_require_enabled',
31 'args' => array(
32 'id' => array(
33 'required' => true,
34 'type' => 'string',
35 'sanitize_callback' => 'sanitize_key',
36 ),
37 'enabled' => array(
38 'required' => true,
39 'type' => 'boolean',
40 'sanitize_callback' => 'rest_sanitize_boolean',
41 ),
42 ),
43 )
44 );
45 }
46 add_action( 'rest_api_init', 'openstation_station_home_register_rest_routes' );
47
48 /**
49 * Return the current Station Home snapshot.
50 *
51 * @return WP_REST_Response
52 */
53 function openstation_station_home_rest_snapshot() {
54 return rest_ensure_response( openstation_station_home_build_snapshot() );
55 }
56
57 /**
58 * Save one explicit card choice for the current user.
59 *
60 * Returning a fresh snapshot lets the client add or remove the card in the
61 * same paint that settles the switch, without inventing a second response
62 * shape for dynamic card data.
63 *
64 * @param WP_REST_Request $request REST request.
65 * @return WP_REST_Response|WP_Error
66 */
67 function openstation_station_home_rest_update_card_preference( WP_REST_Request $request ) {
68 $id = sanitize_key( (string) $request->get_param( 'id' ) );
69 $cards = openstation_station_home_get_registered_cards();
70 if ( '' === $id || ! isset( $cards[ $id ] ) ) {
71 return new WP_Error(
72 'openstation_station_home_card_not_found',
73 __( 'That Station Home card is not available.', 'desktop-mode' ),
74 array( 'status' => 404 )
75 );
76 }
77
78 $user_id = get_current_user_id();
79 $preferences = openstation_station_home_get_card_preferences( $user_id );
80 $enabled = rest_sanitize_boolean( $request->get_param( 'enabled' ) );
81 $preferences[ $id ] = $enabled;
82 update_user_meta( $user_id, OPENSTATION_STATION_HOME_CARD_PREFERENCES_META, $preferences );
83
84 /**
85 * Fires after a user opts in to or out of a Station Home card.
86 *
87 * @param int $user_id User id.
88 * @param string $id Card id.
89 * @param bool $enabled New explicit state.
90 */
91 do_action( 'openstation_station_home_card_preference_updated', $user_id, $id, $enabled );
92
93 return rest_ensure_response( openstation_station_home_build_snapshot() );
94 }
95
96 /**
97 * Post types that the current user can edit and that belong in recent work.
98 *
99 * @return string[]
100 */
101 function openstation_station_home_editable_post_types() {
102 $types = array();
103 foreach ( get_post_types( array( 'show_ui' => true ), 'objects' ) as $type ) {
104 if ( ! is_object( $type ) || 'attachment' === $type->name ) {
105 continue;
106 }
107 // Core's internal editor records (`wp_navigation`, `wp_block`,
108 // templates, styles) are implementation details rather than work a
109 // person expects to resume from a home screen. Keep Posts and Pages,
110 // then admit public UI-visible custom types.
111 if ( ! in_array( $type->name, array( 'post', 'page' ), true ) && ! $type->public ) {
112 continue;
113 }
114 if ( current_user_can( $type->cap->edit_posts ) ) {
115 $types[] = $type->name;
116 }
117 }
118 return array_values( array_unique( $types ) );
119 }
120
121 /**
122 * Query the current user's most recently modified editable content.
123 *
124 * @param string[] $post_types Editable post types.
125 * @return array[]
126 */
127 function openstation_station_home_recent_work( $post_types ) {
128 if ( empty( $post_types ) ) {
129 return array();
130 }
131
132 $query = new WP_Query(
133 array(
134 'post_type' => $post_types,
135 'post_status' => array( 'draft', 'pending', 'future', 'private', 'publish' ),
136 'author' => get_current_user_id(),
137 'posts_per_page' => 5,
138 'orderby' => 'modified',
139 'order' => 'DESC',
140 'ignore_sticky_posts' => true,
141 'no_found_rows' => true,
142 'update_post_meta_cache' => false,
143 'update_post_term_cache' => false,
144 )
145 );
146
147 $items = array();
148 foreach ( $query->posts as $post ) {
149 $edit_url = get_edit_post_link( $post->ID, 'raw' );
150 if ( ! is_string( $edit_url ) || '' === $edit_url ) {
151 continue;
152 }
153
154 $type_object = get_post_type_object( $post->post_type );
155 $status_object = get_post_status_object( $post->post_status );
156 $title = get_the_title( $post );
157 $modified_gmt = (string) get_post_field( 'post_modified_gmt', $post );
158 $modified_iso = '';
159 if ( '' !== $modified_gmt && '0000-00-00 00:00:00' !== $modified_gmt ) {
160 $timestamp = strtotime( $modified_gmt . ' UTC' );
161 if ( false !== $timestamp ) {
162 $modified_iso = gmdate( 'c', $timestamp );
163 }
164 }
165
166 $items[] = array(
167 'id' => (int) $post->ID,
168 'title' => '' !== trim( $title ) ? $title : __( '(Untitled)', 'desktop-mode' ),
169 'typeLabel' => $type_object ? $type_object->labels->singular_name : __( 'Content', 'desktop-mode' ),
170 'icon' => 'page' === $post->post_type ? 'dashicons-admin-page' : 'dashicons-admin-post',
171 'status' => (string) $post->post_status,
172 'statusLabel' => $status_object ? $status_object->label : ucfirst( (string) $post->post_status ),
173 'modifiedGmt' => $modified_iso,
174 'editUrl' => esc_url_raw( $edit_url ),
175 );
176 }
177
178 return $items;
179 }
180
181 /**
182 * Count current-user drafts across the editable Station Home types.
183 *
184 * @param string[] $post_types Editable post types.
185 * @return int
186 */
187 function openstation_station_home_draft_count( $post_types ) {
188 if ( empty( $post_types ) ) {
189 return 0;
190 }
191 $query = new WP_Query(
192 array(
193 'post_type' => $post_types,
194 'post_status' => 'draft',
195 'author' => get_current_user_id(),
196 'posts_per_page' => 1,
197 'fields' => 'ids',
198 'no_found_rows' => false,
199 )
200 );
201 return (int) $query->found_posts;
202 }
203
204 /**
205 * Count published posts and pages on the site.
206 *
207 * @return int
208 */
209 function openstation_station_home_published_count() {
210 $total = 0;
211 foreach ( array( 'post', 'page' ) as $post_type ) {
212 $counts = wp_count_posts( $post_type );
213 $total += isset( $counts->publish ) ? (int) $counts->publish : 0;
214 }
215 return $total;
216 }
217
218 /**
219 * Count image attachments whose alternative text is empty or absent.
220 *
221 * @return int
222 */
223 function openstation_station_home_missing_alt_count() {
224 if ( ! current_user_can( 'upload_files' ) ) {
225 return 0;
226 }
227
228 $query = new WP_Query(
229 array(
230 'post_type' => 'attachment',
231 'post_status' => 'inherit',
232 'post_mime_type' => 'image',
233 'posts_per_page' => 1,
234 'fields' => 'ids',
235 'no_found_rows' => false,
236 'meta_query' => array( // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query -- one bounded dashboard count, only for users who can remediate it.
237 'relation' => 'OR',
238 array(
239 'key' => '_wp_attachment_image_alt',
240 'compare' => 'NOT EXISTS',
241 ),
242 array(
243 'key' => '_wp_attachment_image_alt',
244 'value' => '',
245 'compare' => '=',
246 ),
247 ),
248 )
249 );
250 return (int) $query->found_posts;
251 }
252
253 /**
254 * Read WordPress's cached update totals without making a network request.
255 *
256 * @return int
257 */
258 function openstation_station_home_update_count() {
259 if ( ! current_user_can( 'update_core' ) && ! current_user_can( 'update_plugins' ) && ! current_user_can( 'update_themes' ) ) {
260 return 0;
261 }
262
263 if ( ! function_exists( 'wp_get_update_data' ) ) {
264 require_once ABSPATH . 'wp-admin/includes/update.php';
265 }
266 $data = wp_get_update_data();
267 return isset( $data['counts']['total'] ) ? (int) $data['counts']['total'] : 0;
268 }
269
270 /**
271 * Build capability-aware quick actions.
272 *
273 * @return array[]
274 */
275 function openstation_station_home_quick_actions() {
276 $actions = array();
277 if ( current_user_can( 'edit_posts' ) ) {
278 $actions[] = array(
279 'id' => 'new-post',
280 'label' => __( 'New post', 'desktop-mode' ),
281 'icon' => 'dashicons-edit',
282 'kind' => 'url',
283 'url' => esc_url_raw( admin_url( 'post-new.php' ) ),
284 );
285 }
286 if ( current_user_can( 'upload_files' ) ) {
287 $actions[] = array(
288 'id' => 'upload-media',
289 'label' => __( 'Upload media', 'desktop-mode' ),
290 'icon' => 'dashicons-upload',
291 'kind' => 'url',
292 'url' => esc_url_raw( admin_url( 'media-new.php' ) ),
293 );
294 }
295 $actions[] = array(
296 'id' => 'view-site',
297 'label' => __( 'View site', 'desktop-mode' ),
298 'icon' => 'dashicons-visibility',
299 'kind' => 'external',
300 'url' => esc_url_raw( home_url( '/' ) ),
301 );
302 if ( function_exists( 'openstation_my_wordpress_user_can_use' ) && openstation_my_wordpress_user_can_use() ) {
303 $actions[] = array(
304 'id' => 'wp-explorer',
305 'label' => __( 'WP Explorer', 'desktop-mode' ),
306 'icon' => 'dashicons-open-folder',
307 'kind' => 'native',
308 'windowId' => 'desktop-mode-my-wordpress',
309 );
310 }
311 $actions[] = array(
312 'id' => 'classic-dashboard',
313 'label' => __( 'Classic Dashboard', 'desktop-mode' ),
314 'icon' => 'dashicons-dashboard',
315 'kind' => 'classic',
316 'url' => esc_url_raw( add_query_arg( OPENSTATION_CLASSIC_FLAG, '1', admin_url( 'index.php' ) ) ),
317 );
318 return $actions;
319 }
320
321 /**
322 * Assemble the role-aware snapshot consumed by the Station Home bundle.
323 *
324 * @return array<string, mixed>
325 */
326 function openstation_station_home_build_snapshot() {
327 $user = wp_get_current_user();
328 $post_types = openstation_station_home_editable_post_types();
329 $drafts = openstation_station_home_draft_count( $post_types );
330 $published = openstation_station_home_published_count();
331 $comment_data = wp_count_comments();
332 $pending = current_user_can( 'moderate_comments' ) ? (int) $comment_data->moderated : 0;
333 $updates = openstation_station_home_update_count();
334 $missing_alt = openstation_station_home_missing_alt_count();
335 $first_name = trim( (string) get_user_meta( $user->ID, 'first_name', true ) );
336 $display_name = '' !== $first_name ? $first_name : $user->display_name;
337 $cards = openstation_station_home_build_cards(
338 openstation_station_home_get_registered_cards(),
339 openstation_station_home_get_card_preferences( $user->ID )
340 );
341
342 $attention = array();
343 if ( $pending > 0 ) {
344 $attention[] = array(
345 'id' => 'comments',
346 'icon' => 'dashicons-admin-comments',
347 'count' => $pending,
348 'label' => sprintf(
349 /* translators: %s: pending comment count. */
350 _n( '%s pending comment', '%s pending comments', $pending, 'desktop-mode' ),
351 number_format_i18n( $pending )
352 ),
353 'description' => __( 'Review and reply to keep the conversation moving.', 'desktop-mode' ),
354 'url' => esc_url_raw( admin_url( 'edit-comments.php?comment_status=moderated' ) ),
355 );
356 }
357 if ( $updates > 0 ) {
358 $attention[] = array(
359 'id' => 'updates',
360 'icon' => 'dashicons-update',
361 'count' => $updates,
362 'label' => sprintf(
363 /* translators: %s: update count. */
364 _n( '%s update available', '%s updates available', $updates, 'desktop-mode' ),
365 number_format_i18n( $updates )
366 ),
367 'description' => __( 'Keep WordPress, themes, and plugins current.', 'desktop-mode' ),
368 'url' => esc_url_raw( admin_url( 'update-core.php' ) ),
369 );
370 }
371 if ( $missing_alt > 0 ) {
372 $attention[] = array(
373 'id' => 'missing-alt',
374 'icon' => 'dashicons-format-image',
375 'count' => $missing_alt,
376 'label' => sprintf(
377 /* translators: %s: image count. */
378 _n( '%s image needs alt text', '%s images need alt text', $missing_alt, 'desktop-mode' ),
379 number_format_i18n( $missing_alt )
380 ),
381 'description' => __( 'Add a useful description for people using assistive technology.', 'desktop-mode' ),
382 'url' => esc_url_raw( admin_url( 'upload.php?mode=list' ) ),
383 );
384 }
385
386 return array(
387 'userName' => $display_name,
388 'siteName' => get_bloginfo( 'name' ),
389 'work' => openstation_station_home_recent_work( $post_types ),
390 'quickActions' => openstation_station_home_quick_actions(),
391 'metrics' => array(
392 array(
393 'id' => 'drafts',
394 'label' => __( 'Drafts', 'desktop-mode' ),
395 'icon' => 'dashicons-edit',
396 'value' => $drafts,
397 ),
398 array(
399 'id' => 'comments',
400 'label' => __( 'Pending comments', 'desktop-mode' ),
401 'icon' => 'dashicons-admin-comments',
402 'value' => $pending,
403 ),
404 array(
405 'id' => 'updates',
406 'label' => __( 'Updates', 'desktop-mode' ),
407 'icon' => 'dashicons-update',
408 'value' => $updates,
409 ),
410 array(
411 'id' => 'published',
412 'label' => __( 'Published', 'desktop-mode' ),
413 'icon' => 'dashicons-admin-site-alt3',
414 'value' => $published,
415 ),
416 ),
417 'attention' => $attention,
418 'cards' => $cards['cards'],
419 'cardPreferences' => $cards['preferences'],
420 );
421 }
422