PluginProbe
OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin / 1.1.10
OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin v1.1.10
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
← All changes | includes/notes/rest.php +204 -90 0.9.81.1.10 View file →
@@ -1,7 +1,7 @@
1 1 <?php
2 2 /**
3 - * Desktop Mode — Pinned notes REST routes.
3 + * OpenStation — Pinned notes REST routes.
4 4 *
5 5 * Routes under `/desktop-mode/v1/notes`:
6 6 *
7 7 * GET /notes List the viewer's own notes
@@ -21,27 +21,27 @@
21 21 * notes" is not a supported operation through this controller.
22 22 *
23 23 * Optimistic concurrency: PATCH accepts an `updatedAtMs` field
24 24 * carrying the client's last-seen modified timestamp; a mismatch
25 - * returns 409 `desktop_mode_notes_conflict` with the server copy in
25 + * returns 409 `openstation_notes_conflict` with the server copy in
26 26 * `data.current` so the client can re-render instead of clobbering.
27 27 *
28 - * @package WPDesktopMode
28 + * @package OpenStation
29 29 */
30 30
31 31 defined( 'ABSPATH' ) || exit;
32 32
33 33 /**
34 - * Base permission: logged-in + desktop mode enabled.
34 + * Base permission: logged-in + OpenStation enabled.
35 35 *
36 36 * @return true|WP_Error
37 37 */
38 -function desktop_mode_notes_rest_permission() {
38 +function openstation_notes_rest_permission() {
39 39 if ( ! is_user_logged_in() ) {
40 - return new WP_Error( 'desktop_mode_notes_unauthenticated', __( 'You must be logged in.', 'desktop-mode' ), array( 'status' => 401 ) );
40 + return new WP_Error( 'openstation_notes_unauthenticated', __( 'You must be logged in.', 'desktop-mode' ), array( 'status' => 401 ) );
41 41 }
42 - if ( function_exists( 'desktop_mode_is_enabled' ) && ! desktop_mode_is_enabled( get_current_user_id() ) ) {
43 - return new WP_Error( 'desktop_mode_notes_disabled', __( 'Desktop mode is not enabled for this user.', 'desktop-mode' ), array( 'status' => 403 ) );
42 + if ( function_exists( 'openstation_is_enabled' ) && ! openstation_is_enabled( get_current_user_id() ) ) {
43 + return new WP_Error( 'openstation_notes_disabled', __( 'OpenStation is not enabled for this user.', 'desktop-mode' ), array( 'status' => 403 ) );
44 44 }
45 45 return true;
46 46 }
47 47
@@ -47,9 +47,9 @@
47 47
48 48 /**
49 49 * Register the routes.
50 50 */
51 -function desktop_mode_notes_register_rest_routes() {
51 +function openstation_notes_register_rest_routes() {
52 52 $ns = 'desktop-mode/v1';
53 53
54 54 register_rest_route(
55 55 $ns,
@@ -56,15 +56,15 @@
56 56 '/notes',
57 57 array(
58 58 array(
59 59 'methods' => WP_REST_Server::READABLE,
60 - 'permission_callback' => 'desktop_mode_notes_rest_permission',
61 - 'callback' => 'desktop_mode_notes_rest_list',
60 + 'permission_callback' => 'openstation_notes_rest_permission',
61 + 'callback' => 'openstation_notes_rest_list',
62 62 ),
63 63 array(
64 64 'methods' => WP_REST_Server::CREATABLE,
65 - 'permission_callback' => 'desktop_mode_notes_rest_permission',
66 - 'callback' => 'desktop_mode_notes_rest_create',
65 + 'permission_callback' => 'openstation_notes_rest_permission',
66 + 'callback' => 'openstation_notes_rest_create',
67 67 'args' => array(
68 68 'text' => array(
69 69 'type' => 'string',
70 70 'default' => '',
@@ -72,13 +72,22 @@
72 72 ),
73 73 'color' => array(
74 74 'type' => 'string',
75 75 'default' => 'butter',
76 - 'sanitize_callback' => 'desktop_mode_notes_sanitize_color',
76 + 'sanitize_callback' => 'openstation_notes_sanitize_color',
77 77 ),
78 - 'x' => array( 'type' => 'number', 'default' => 0.1 ),
79 - 'y' => array( 'type' => 'number', 'default' => 0.1 ),
80 - 'public' => array( 'type' => 'boolean', 'default' => false ),
78 + 'x' => array(
79 + 'type' => 'number',
80 + 'default' => 0.1,
81 + ),
82 + 'y' => array(
83 + 'type' => 'number',
84 + 'default' => 0.1,
85 + ),
86 + 'public' => array(
87 + 'type' => 'boolean',
88 + 'default' => false,
89 + ),
81 90 'seed' => array(
82 91 'type' => 'integer',
83 92 'default' => 0,
84 93 'sanitize_callback' => 'absint',
@@ -93,15 +102,15 @@
93 102 '/notes/(?P<id>\d+)',
94 103 array(
95 104 array(
96 105 'methods' => WP_REST_Server::EDITABLE,
97 - 'permission_callback' => 'desktop_mode_notes_rest_permission',
98 - 'callback' => 'desktop_mode_notes_rest_update',
106 + 'permission_callback' => 'openstation_notes_rest_permission',
107 + 'callback' => 'openstation_notes_rest_update',
99 108 ),
100 109 array(
101 110 'methods' => WP_REST_Server::DELETABLE,
102 - 'permission_callback' => 'desktop_mode_notes_rest_permission',
103 - 'callback' => 'desktop_mode_notes_rest_delete',
111 + 'permission_callback' => 'openstation_notes_rest_permission',
112 + 'callback' => 'openstation_notes_rest_delete',
104 113 ),
105 114 )
106 115 );
107 116
@@ -109,10 +118,10 @@
109 118 $ns,
110 119 '/notes/(?P<id>\d+)/restore',
111 120 array(
112 121 'methods' => WP_REST_Server::CREATABLE,
113 - 'permission_callback' => 'desktop_mode_notes_rest_permission',
114 - 'callback' => 'desktop_mode_notes_rest_restore',
122 + 'permission_callback' => 'openstation_notes_rest_permission',
123 + 'callback' => 'openstation_notes_rest_restore',
115 124 )
116 125 );
117 126
118 127 register_rest_route(
@@ -119,14 +128,14 @@
119 128 $ns,
120 129 '/notes/(?P<id>\d+)/convert',
121 130 array(
122 131 'methods' => WP_REST_Server::CREATABLE,
123 - 'permission_callback' => 'desktop_mode_notes_rest_permission',
124 - 'callback' => 'desktop_mode_notes_rest_convert',
132 + 'permission_callback' => 'openstation_notes_rest_permission',
133 + 'callback' => 'openstation_notes_rest_convert',
125 134 )
126 135 );
127 136 }
128 -add_action( 'rest_api_init', 'desktop_mode_notes_register_rest_routes' );
137 +add_action( 'rest_api_init', 'openstation_notes_register_rest_routes' );
129 138
130 139 /**
131 140 * Fetch a note post, or a WP_Error when it doesn't exist / isn't a note.
132 141 *
@@ -133,16 +142,16 @@
133 142 * @param int $id Post ID.
134 143 * @param bool $allow_trash Whether a trashed note is acceptable (restore path).
135 144 * @return WP_Post|WP_Error
136 145 */
137 -function desktop_mode_notes_get_note( $id, $allow_trash = false ) {
146 +function openstation_notes_get_note( $id, $allow_trash = false ) {
138 147 $post = get_post( (int) $id );
139 - if ( ! $post instanceof WP_Post || DESKTOP_MODE_NOTES_POST_TYPE !== $post->post_type ) {
140 - return new WP_Error( 'desktop_mode_notes_not_found', __( 'Note not found.', 'desktop-mode' ), array( 'status' => 404 ) );
148 + if ( ! $post instanceof WP_Post || OPENSTATION_NOTES_POST_TYPE !== $post->post_type ) {
149 + return new WP_Error( 'openstation_notes_not_found', __( 'Note not found.', 'desktop-mode' ), array( 'status' => 404 ) );
141 150 }
142 151 $allowed = $allow_trash ? array( 'private', 'publish', 'trash' ) : array( 'private', 'publish' );
143 152 if ( ! in_array( $post->post_status, $allowed, true ) ) {
144 - return new WP_Error( 'desktop_mode_notes_not_found', __( 'Note not found.', 'desktop-mode' ), array( 'status' => 404 ) );
153 + return new WP_Error( 'openstation_notes_not_found', __( 'Note not found.', 'desktop-mode' ), array( 'status' => 404 ) );
145 154 }
146 155 return $post;
147 156 }
148 157
@@ -155,11 +164,11 @@
155 164 *
156 165 * @param WP_Post $post Note post.
157 166 * @return true|WP_Error
158 167 */
159 -function desktop_mode_notes_require_owner( $post ) {
160 - if ( (int) $post->post_author !== get_current_user_id() ) {
161 - return new WP_Error( 'desktop_mode_notes_forbidden', __( 'Only the note owner can change it.', 'desktop-mode' ), array( 'status' => 403 ) );
168 +function openstation_notes_require_owner( $post ) {
169 + if ( get_current_user_id() !== (int) $post->post_author ) {
170 + return new WP_Error( 'openstation_notes_forbidden', __( 'Only the note owner can change it.', 'desktop-mode' ), array( 'status' => 403 ) );
162 171 }
163 172 return true;
164 173 }
165 174
@@ -171,9 +180,9 @@
171 180 *
172 181 * @param WP_Post $post Post.
173 182 * @return int
174 183 */
175 -function desktop_mode_notes_modified_ms( $post ) {
184 +function openstation_notes_modified_ms( $post ) {
176 185 return (int) get_post_modified_time( 'U', true, $post ) * 1000;
177 186 }
178 187
179 188 /**
@@ -181,9 +190,9 @@
181 190 *
182 191 * @param WP_Post $post Note post.
183 192 * @return array
184 193 */
185 -function desktop_mode_notes_prepare( $post ) {
194 +function openstation_notes_prepare( $post ) {
186 195 $owner_id = (int) $post->post_author;
187 196 $owner = get_userdata( $owner_id );
188 197
189 198 return array(
@@ -188,11 +197,11 @@
188 197
189 198 return array(
190 199 'id' => (int) $post->ID,
191 200 'text' => (string) get_post_field( 'post_content', $post, 'raw' ),
192 - 'color' => desktop_mode_notes_sanitize_color( get_post_meta( $post->ID, '_wpd_note_color', true ) ),
193 - 'x' => desktop_mode_notes_sanitize_fraction( get_post_meta( $post->ID, '_wpd_note_x', true ) ),
194 - 'y' => desktop_mode_notes_sanitize_fraction( get_post_meta( $post->ID, '_wpd_note_y', true ) ),
201 + 'color' => openstation_notes_sanitize_color( get_post_meta( $post->ID, '_wpd_note_color', true ) ),
202 + 'x' => openstation_notes_sanitize_fraction( get_post_meta( $post->ID, '_wpd_note_x', true ) ),
203 + 'y' => openstation_notes_sanitize_fraction( get_post_meta( $post->ID, '_wpd_note_y', true ) ),
195 204 'z' => (int) get_post_meta( $post->ID, '_wpd_note_z', true ),
196 205 'public' => 'publish' === $post->post_status,
197 206 'seed' => (int) get_post_meta( $post->ID, '_wpd_note_seed', true ),
198 207 'ownerId' => $owner_id,
@@ -198,9 +207,9 @@
198 207 'ownerId' => $owner_id,
199 208 'ownerName' => $owner instanceof WP_User ? (string) $owner->display_name : '',
200 209 'ownerAvatar' => (string) get_avatar_url( $owner_id, array( 'size' => 48 ) ),
201 210 'canEdit' => get_current_user_id() === $owner_id,
202 - 'updatedAtMs' => desktop_mode_notes_modified_ms( $post ),
211 + 'updatedAtMs' => openstation_notes_modified_ms( $post ),
203 212 );
204 213 }
205 214
206 215 /**
@@ -210,9 +219,9 @@
210 219 *
211 220 * @param string $text Note text.
212 221 * @return string
213 222 */
214 -function desktop_mode_notes_derive_title( $text ) {
223 +function openstation_notes_derive_title( $text ) {
215 224 foreach ( preg_split( '/\r\n|\r|\n/', (string) $text ) as $line ) {
216 225 $line = trim( $line );
217 226 if ( '' !== $line ) {
218 227 return mb_substr( sanitize_text_field( $line ), 0, 80 );
@@ -225,9 +234,9 @@
225 234 * GET /notes — own notes (private + publish) ∪ others' publish.
226 235 *
227 236 * @return WP_REST_Response
228 237 */
229 -function desktop_mode_notes_rest_list() {
238 +function openstation_notes_rest_list() {
230 239 $user_id = get_current_user_id();
231 240
232 241 // Newest first: the per-half cap exists as a runaway guard, and
233 242 // when it ever bites it must drop the OLDEST notes — capping an
@@ -235,9 +244,9 @@
235 244 // (and the boot high-water would stop the Heartbeat delta from
236 245 // ever backfilling them).
237 246 $own = new WP_Query(
238 247 array(
239 - 'post_type' => DESKTOP_MODE_NOTES_POST_TYPE,
248 + 'post_type' => OPENSTATION_NOTES_POST_TYPE,
240 249 'post_status' => array( 'private', 'publish' ),
241 250 'author' => $user_id,
242 251 'posts_per_page' => 200,
243 252 'orderby' => 'date',
@@ -247,9 +256,9 @@
247 256 );
248 257
249 258 $public = new WP_Query(
250 259 array(
251 - 'post_type' => DESKTOP_MODE_NOTES_POST_TYPE,
260 + 'post_type' => OPENSTATION_NOTES_POST_TYPE,
252 261 'post_status' => 'publish',
253 262 'author__not_in' => array( $user_id ),
254 263 'posts_per_page' => 200,
255 264 'orderby' => 'date',
@@ -259,9 +268,9 @@
259 268 );
260 269
261 270 $notes = array();
262 271 foreach ( array_merge( (array) $own->posts, (array) $public->posts ) as $post ) {
263 - $notes[] = desktop_mode_notes_prepare( $post );
272 + $notes[] = openstation_notes_prepare( $post );
264 273 }
265 274 wp_reset_postdata();
266 275
267 276 return rest_ensure_response( array( 'notes' => $notes ) );
@@ -272,13 +281,13 @@
272 281 *
273 282 * @param WP_REST_Request $request Request.
274 283 * @return WP_REST_Response|WP_Error
275 284 */
276 -function desktop_mode_notes_rest_create( $request ) {
285 +function openstation_notes_rest_create( $request ) {
277 286 /**
278 287 * Filters whether the current user may create a note.
279 288 *
280 - * Notes default to any logged-in desktop-mode user — including
289 + * Notes default to any logged-in openstation user — including
281 290 * publishing PUBLIC notes onto every other user's wallpaper.
282 291 * Sites that want to restrict that (by role, capability, or the
283 292 * request's `public` flag) hook here.
284 293 *
@@ -285,11 +294,11 @@
285 294 * @param bool $can_create Whether creation is allowed. Default true.
286 295 * @param int $user_id Current user id.
287 296 * @param WP_REST_Request $request The create request (inspect `public`, `text`, ...).
288 297 */
289 - $can_create = apply_filters( 'desktop_mode_notes_user_can_create', true, get_current_user_id(), $request );
298 + $can_create = apply_filters( 'openstation_notes_user_can_create', true, get_current_user_id(), $request );
290 299 if ( ! $can_create ) {
291 - return new WP_Error( 'desktop_mode_notes_forbidden', __( 'You are not allowed to create notes.', 'desktop-mode' ), array( 'status' => 403 ) );
300 + return new WP_Error( 'openstation_notes_forbidden', __( 'You are not allowed to create notes.', 'desktop-mode' ), array( 'status' => 403 ) );
292 301 }
293 302
294 303 $text = sanitize_textarea_field( (string) $request['text'] );
295 304
@@ -294,12 +303,12 @@
294 303 $text = sanitize_textarea_field( (string) $request['text'] );
295 304
296 305 $post_id = wp_insert_post(
297 306 array(
298 - 'post_type' => DESKTOP_MODE_NOTES_POST_TYPE,
307 + 'post_type' => OPENSTATION_NOTES_POST_TYPE,
299 308 'post_status' => $request['public'] ? 'publish' : 'private',
300 309 'post_author' => get_current_user_id(),
301 - 'post_title' => desktop_mode_notes_derive_title( $text ),
310 + 'post_title' => openstation_notes_derive_title( $text ),
302 311 'post_content' => $text,
303 312 ),
304 313 true
305 314 );
@@ -307,12 +316,12 @@
307 316 $post_id->add_data( array( 'status' => 500 ) );
308 317 return $post_id;
309 318 }
310 319
311 - update_post_meta( $post_id, '_wpd_note_color', desktop_mode_notes_sanitize_color( $request['color'] ) );
312 - update_post_meta( $post_id, '_wpd_note_x', desktop_mode_notes_sanitize_fraction( $request['x'] ) );
313 - update_post_meta( $post_id, '_wpd_note_y', desktop_mode_notes_sanitize_fraction( $request['y'] ) );
314 - update_post_meta( $post_id, '_wpd_note_z', desktop_mode_notes_next_z() );
320 + update_post_meta( $post_id, '_wpd_note_color', openstation_notes_sanitize_color( $request['color'] ) );
321 + update_post_meta( $post_id, '_wpd_note_x', openstation_notes_sanitize_fraction( $request['x'] ) );
322 + update_post_meta( $post_id, '_wpd_note_y', openstation_notes_sanitize_fraction( $request['y'] ) );
323 + update_post_meta( $post_id, '_wpd_note_z', openstation_notes_next_z() );
315 324 // The jitter seed is written ONCE, here — PATCH never touches it,
316 325 // so editing a note's text never re-tilts its paper. The client
317 326 // sends its own text hash (keeps the optimistic render identical);
318 327 // fall back to a server-side hash when absent.
@@ -322,9 +331,9 @@
322 331 $seed = $seed > 0 ? $seed : 1;
323 332 }
324 333 update_post_meta( $post_id, '_wpd_note_seed', $seed );
325 334
326 - return rest_ensure_response( desktop_mode_notes_prepare( get_post( $post_id ) ) );
335 + return rest_ensure_response( openstation_notes_prepare( get_post( $post_id ) ) );
327 336 }
328 337
329 338 /**
330 339 * Next z-order value across all live (non-trashed) notes.
@@ -335,9 +344,9 @@
335 344 * (a wall of paper, not a database of record).
336 345 *
337 346 * @return int
338 347 */
339 -function desktop_mode_notes_next_z() {
348 +function openstation_notes_next_z() {
340 349 global $wpdb;
341 350 $max = $wpdb->get_var(
342 351 $wpdb->prepare(
343 352 "SELECT MAX( CAST( pm.meta_value AS UNSIGNED ) )
@@ -344,9 +353,9 @@
344 353 FROM {$wpdb->postmeta} pm
345 354 INNER JOIN {$wpdb->posts} p ON p.ID = pm.post_id
346 355 WHERE pm.meta_key = %s AND p.post_type = %s AND p.post_status IN ( 'private', 'publish' )",
347 356 '_wpd_note_z',
348 - DESKTOP_MODE_NOTES_POST_TYPE
357 + OPENSTATION_NOTES_POST_TYPE
349 358 )
350 359 );
351 360 return (int) $max + 1;
352 361 }
@@ -356,14 +365,14 @@
356 365 *
357 366 * @param WP_REST_Request $request Request.
358 367 * @return WP_REST_Response|WP_Error
359 368 */
360 -function desktop_mode_notes_rest_update( $request ) {
361 - $post = desktop_mode_notes_get_note( $request['id'] );
369 +function openstation_notes_rest_update( $request ) {
370 + $post = openstation_notes_get_note( $request['id'] );
362 371 if ( is_wp_error( $post ) ) {
363 372 return $post;
364 373 }
365 - $owner = desktop_mode_notes_require_owner( $post );
374 + $owner = openstation_notes_require_owner( $post );
366 375 if ( is_wp_error( $owner ) ) {
367 376 return $owner;
368 377 }
369 378
@@ -369,15 +378,15 @@
369 378
370 379 // Optimistic concurrency — a stale token means another session
371 380 // (or device) changed the note since this client last saw it.
372 381 $client_ms = $request['updatedAtMs'];
373 - if ( null !== $client_ms && (int) $client_ms !== desktop_mode_notes_modified_ms( $post ) ) {
382 + if ( null !== $client_ms && openstation_notes_modified_ms( $post ) !== (int) $client_ms ) {
374 383 return new WP_Error(
375 - 'desktop_mode_notes_conflict',
384 + 'openstation_notes_conflict',
376 385 __( 'The note was changed by another session.', 'desktop-mode' ),
377 386 array(
378 387 'status' => 409,
379 - 'current' => desktop_mode_notes_prepare( $post ),
388 + 'current' => openstation_notes_prepare( $post ),
380 389 )
381 390 );
382 391 }
383 392
@@ -385,9 +394,9 @@
385 394
386 395 if ( null !== $request['text'] ) {
387 396 $text = sanitize_textarea_field( (string) $request['text'] );
388 397 $update['post_content'] = $text;
389 - $update['post_title'] = desktop_mode_notes_derive_title( $text );
398 + $update['post_title'] = openstation_notes_derive_title( $text );
390 399 }
391 400 if ( null !== $request['public'] ) {
392 401 $update['post_status'] = rest_sanitize_boolean( $request['public'] ) ? 'publish' : 'private';
393 402 }
@@ -392,15 +401,15 @@
392 401 $update['post_status'] = rest_sanitize_boolean( $request['public'] ) ? 'publish' : 'private';
393 402 }
394 403
395 404 if ( null !== $request['color'] ) {
396 - update_post_meta( $post->ID, '_wpd_note_color', desktop_mode_notes_sanitize_color( $request['color'] ) );
405 + update_post_meta( $post->ID, '_wpd_note_color', openstation_notes_sanitize_color( $request['color'] ) );
397 406 }
398 407 if ( null !== $request['x'] ) {
399 - update_post_meta( $post->ID, '_wpd_note_x', desktop_mode_notes_sanitize_fraction( $request['x'] ) );
408 + update_post_meta( $post->ID, '_wpd_note_x', openstation_notes_sanitize_fraction( $request['x'] ) );
400 409 }
401 410 if ( null !== $request['y'] ) {
402 - update_post_meta( $post->ID, '_wpd_note_y', desktop_mode_notes_sanitize_fraction( $request['y'] ) );
411 + update_post_meta( $post->ID, '_wpd_note_y', openstation_notes_sanitize_fraction( $request['y'] ) );
403 412 }
404 413 if ( null !== $request['z'] ) {
405 414 update_post_meta( $post->ID, '_wpd_note_z', absint( $request['z'] ) );
406 415 }
@@ -413,9 +422,9 @@
413 422 $result->add_data( array( 'status' => 500 ) );
414 423 return $result;
415 424 }
416 425
417 - return rest_ensure_response( desktop_mode_notes_prepare( get_post( $post->ID ) ) );
426 + return rest_ensure_response( openstation_notes_prepare( get_post( $post->ID ) ) );
418 427 }
419 428
420 429 /**
421 430 * DELETE /notes/:id — soft-trash, owner only.
@@ -422,23 +431,28 @@
422 431 *
423 432 * @param WP_REST_Request $request Request.
424 433 * @return WP_REST_Response|WP_Error
425 434 */
426 -function desktop_mode_notes_rest_delete( $request ) {
427 - $post = desktop_mode_notes_get_note( $request['id'] );
435 +function openstation_notes_rest_delete( $request ) {
436 + $post = openstation_notes_get_note( $request['id'] );
428 437 if ( is_wp_error( $post ) ) {
429 438 return $post;
430 439 }
431 - $owner = desktop_mode_notes_require_owner( $post );
440 + $owner = openstation_notes_require_owner( $post );
432 441 if ( is_wp_error( $owner ) ) {
433 442 return $owner;
434 443 }
435 444
436 445 if ( ! wp_trash_post( $post->ID ) ) {
437 - return new WP_Error( 'desktop_mode_notes_trash_failed', __( 'Could not move the note to the trash.', 'desktop-mode' ), array( 'status' => 500 ) );
446 + return new WP_Error( 'openstation_notes_trash_failed', __( 'Could not move the note to the trash.', 'desktop-mode' ), array( 'status' => 500 ) );
438 447 }
439 448
440 - return rest_ensure_response( array( 'trashed' => true, 'id' => (int) $post->ID ) );
449 + return rest_ensure_response(
450 + array(
451 + 'trashed' => true,
452 + 'id' => (int) $post->ID,
453 + )
454 + );
441 455 }
442 456
443 457 /**
444 458 * POST /notes/:id/restore — untrash (Undo), owner only.
@@ -445,23 +459,23 @@
445 459 *
446 460 * @param WP_REST_Request $request Request.
447 461 * @return WP_REST_Response|WP_Error
448 462 */
449 -function desktop_mode_notes_rest_restore( $request ) {
450 - $post = desktop_mode_notes_get_note( $request['id'], true );
463 +function openstation_notes_rest_restore( $request ) {
464 + $post = openstation_notes_get_note( $request['id'], true );
451 465 if ( is_wp_error( $post ) ) {
452 466 return $post;
453 467 }
454 - $owner = desktop_mode_notes_require_owner( $post );
468 + $owner = openstation_notes_require_owner( $post );
455 469 if ( is_wp_error( $owner ) ) {
456 470 return $owner;
457 471 }
458 472 if ( 'trash' !== $post->post_status ) {
459 - return rest_ensure_response( desktop_mode_notes_prepare( $post ) );
473 + return rest_ensure_response( openstation_notes_prepare( $post ) );
460 474 }
461 475
462 476 if ( ! wp_untrash_post( $post->ID ) ) {
463 - return new WP_Error( 'desktop_mode_notes_restore_failed', __( 'Could not restore the note.', 'desktop-mode' ), array( 'status' => 500 ) );
477 + return new WP_Error( 'openstation_notes_restore_failed', __( 'Could not restore the note.', 'desktop-mode' ), array( 'status' => 500 ) );
464 478 }
465 479
466 480 // If this note was trashed by a "convert to post" action, undoing
467 481 // the conversion must also discard the draft it spawned — otherwise
@@ -477,9 +491,9 @@
477 491 wp_trash_post( $converted_post_id );
478 492 }
479 493 }
480 494
481 - return rest_ensure_response( desktop_mode_notes_prepare( get_post( $post->ID ) ) );
495 + return rest_ensure_response( openstation_notes_prepare( get_post( $post->ID ) ) );
482 496 }
483 497
484 498 /**
485 499 * Convert a note's plain text into Gutenberg paragraph-block markup.
@@ -490,9 +504,9 @@
490 504 *
491 505 * @param string $text Note text.
492 506 * @return string Serialized block markup (empty string for empty text).
493 507 */
494 -function desktop_mode_notes_text_to_blocks( $text ) {
508 +function openstation_notes_text_to_blocks( $text ) {
495 509 $text = str_replace( array( "\r\n", "\r" ), "\n", (string) $text );
496 510 $paragraphs = preg_split( '/\n{2,}/', trim( $text ) );
497 511 $blocks = array();
498 512 foreach ( $paragraphs as $paragraph ) {
@@ -511,28 +525,28 @@
511 525 * the note. Owner only, and the owner must be able to author posts.
512 526 *
513 527 * The note is trashed (not hard-deleted) and linked to its new draft
514 528 * via `_wpd_note_converted_post` so the standard restore route can undo
515 - * both sides of the conversion (see `desktop_mode_notes_rest_restore`).
529 + * both sides of the conversion (see `openstation_notes_rest_restore`).
516 530 *
517 531 * @param WP_REST_Request $request Request.
518 532 * @return WP_REST_Response|WP_Error
519 533 */
520 -function desktop_mode_notes_rest_convert( $request ) {
521 - $post = desktop_mode_notes_get_note( $request['id'] );
534 +function openstation_notes_rest_convert( $request ) {
535 + $post = openstation_notes_get_note( $request['id'] );
522 536 if ( is_wp_error( $post ) ) {
523 537 return $post;
524 538 }
525 - $owner = desktop_mode_notes_require_owner( $post );
539 + $owner = openstation_notes_require_owner( $post );
526 540 if ( is_wp_error( $owner ) ) {
527 541 return $owner;
528 542 }
529 543 if ( ! current_user_can( 'edit_posts' ) ) {
530 - return new WP_Error( 'desktop_mode_notes_cannot_create_posts', __( 'You are not allowed to create posts.', 'desktop-mode' ), array( 'status' => 403 ) );
544 + return new WP_Error( 'openstation_notes_cannot_create_posts', __( 'You are not allowed to create posts.', 'desktop-mode' ), array( 'status' => 403 ) );
531 545 }
532 546
533 547 $text = (string) get_post_field( 'post_content', $post, 'raw' );
534 - $title = desktop_mode_notes_derive_title( $text );
548 + $title = openstation_notes_derive_title( $text );
535 549
536 550 /**
537 551 * Filters the arguments used to create the draft post from a note.
538 552 *
@@ -543,15 +557,15 @@
543 557 * @param WP_Post $post The source note.
544 558 * @param WP_REST_Request $request The convert request.
545 559 */
546 560 $post_args = apply_filters(
547 - 'desktop_mode_notes_convert_post_args',
561 + 'openstation_notes_convert_post_args',
548 562 array(
549 563 'post_type' => 'post',
550 564 'post_status' => 'draft',
551 565 'post_author' => (int) $post->post_author,
552 566 'post_title' => $title,
553 - 'post_content' => desktop_mode_notes_text_to_blocks( $text ),
567 + 'post_content' => openstation_notes_text_to_blocks( $text ),
554 568 ),
555 569 $post,
556 570 $request
557 571 );
@@ -568,9 +582,9 @@
568 582 update_post_meta( $post->ID, '_wpd_note_converted_post', (int) $new_post_id );
569 583 if ( ! wp_trash_post( $post->ID ) ) {
570 584 wp_delete_post( $new_post_id, true );
571 585 delete_post_meta( $post->ID, '_wpd_note_converted_post' );
572 - return new WP_Error( 'desktop_mode_notes_convert_failed', __( 'Could not convert the note to a post.', 'desktop-mode' ), array( 'status' => 500 ) );
586 + return new WP_Error( 'openstation_notes_convert_failed', __( 'Could not convert the note to a post.', 'desktop-mode' ), array( 'status' => 500 ) );
573 587 }
574 588
575 589 /**
576 590 * Fires after a note has been converted to a draft post.
@@ -578,9 +592,9 @@
578 592 * @param int $new_post_id The draft post id.
579 593 * @param WP_Post $post The source note (now trashed).
580 594 * @param WP_REST_Request $request The convert request.
581 595 */
582 - do_action( 'desktop_mode_notes_converted', (int) $new_post_id, $post, $request );
596 + do_action( 'openstation_notes_converted', (int) $new_post_id, $post, $request );
583 597
584 598 return rest_ensure_response(
585 599 array(
586 600 'noteId' => (int) $post->ID,
@@ -588,4 +602,104 @@
588 602 'editUrl' => (string) get_edit_post_link( $new_post_id, 'raw' ),
589 603 )
590 604 );
591 605 }
606 +
607 +/**
608 + * Whether the current user's desktop would show any pinned notes.
609 + *
610 + * The presence hint the boot config ships as `hasNotes`: the notes
611 + * bundle is presence-gated client-side (`src/notes/sentinel.ts`), so
612 + * a user with no notes never downloads it — and never fires the
613 + * boot-time list request the layer used to make unconditionally.
614 + *
615 + * **Steady-state cost: zero queries.** The computed answer is cached
616 + * in user meta, stamped with a revision the site bumps whenever any
617 + * note changes (`openstation_notes_bump_rev()` below). The revision
618 + * lives in an autoloaded option and the user's meta cache is already
619 + * primed on every admin request, so a boot between note changes
620 + * reads two warm caches and touches the database not at all. Only
621 + * the first boot after a note is created / deleted / re-scoped runs
622 + * the probes again — two `fields => ids`, one-row queries at most:
623 + * anyone's public note first, the user's own private ones second,
624 + * mirroring the visibility rule the list route enforces.
625 + *
626 + * @return bool
627 + */
628 +function openstation_notes_user_has_any() {
629 + $rev = (string) get_option( 'desktop_mode_notes_rev', '0' );
630 + $user_id = get_current_user_id();
631 + $cached = (string) get_user_meta( $user_id, '_desktop_mode_has_notes', true );
632 + if ( '' !== $cached ) {
633 + list( $cached_rev, $cached_value ) = array_pad( explode( ':', $cached, 2 ), 2, '' );
634 + if ( $cached_rev === $rev ) {
635 + return '1' === $cached_value;
636 + }
637 + }
638 +
639 + $public = new WP_Query(
640 + array(
641 + 'post_type' => OPENSTATION_NOTES_POST_TYPE,
642 + 'post_status' => 'publish',
643 + 'posts_per_page' => 1,
644 + 'fields' => 'ids',
645 + 'no_found_rows' => true,
646 + )
647 + );
648 + $has = (bool) $public->posts;
649 + if ( ! $has ) {
650 + $own = new WP_Query(
651 + array(
652 + 'post_type' => OPENSTATION_NOTES_POST_TYPE,
653 + 'post_status' => 'private',
654 + 'author' => $user_id,
655 + 'posts_per_page' => 1,
656 + 'fields' => 'ids',
657 + 'no_found_rows' => true,
658 + )
659 + );
660 + $has = (bool) $own->posts;
661 + }
662 +
663 + update_user_meta( $user_id, '_desktop_mode_has_notes', $rev . ':' . ( $has ? '1' : '0' ) );
664 + return $has;
665 +}
666 +
667 +/**
668 + * Invalidate every user's cached `hasNotes` answer.
669 + *
670 + * One autoloaded revision counter instead of per-user cache deletes:
671 + * a public note's existence changes the answer for EVERY user, and
672 + * enumerating users to clear meta would be the expensive thing this
673 + * cache exists to avoid. Bumping the rev makes all stamped answers
674 + * stale at the cost of one option write per note change — and note
675 + * changes are rare next to boots.
676 + *
677 + * @param int|WP_Post $post Post id or object being changed.
678 + * @return void
679 + */
680 +function openstation_notes_bump_rev( $post ) {
681 + $post = get_post( $post );
682 + if ( ! $post instanceof WP_Post || OPENSTATION_NOTES_POST_TYPE !== $post->post_type ) {
683 + return;
684 + }
685 + update_option( 'desktop_mode_notes_rev', (string) time() . '.' . wp_rand( 0, 999 ), true );
686 +}
687 +
688 +/**
689 + * Every path a note can change through: status moves (create,
690 + * publish/private flips, trash, restore) fire
691 + * `transition_post_status`; hard deletes fire `deleted_post`.
692 + *
693 + * @param string $new_status New status.
694 + * @param string $old_status Old status.
695 + * @param WP_Post $post The post.
696 + * @return void
697 + */
698 +function openstation_notes_bump_rev_on_transition( $new_status, $old_status, $post ) {
699 + if ( $new_status === $old_status ) {
700 + return;
701 + }
702 + openstation_notes_bump_rev( $post );
703 +}
704 +add_action( 'transition_post_status', 'openstation_notes_bump_rev_on_transition', 10, 3 );
705 +add_action( 'deleted_post', 'openstation_notes_bump_rev' );