PluginProbe
OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin / 0.8.7
OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin v0.8.7
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 / seen-intros.php

seen-intros.php in OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin 0.8.7, at includes/seen-intros.php

242 lines 6.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Desktop Mode — "Seen intros" registry.
4 *
5 * Tracks which one-time introduction dialogs the current user has
6 * already dismissed, so the shell can show a "what's new in this
7 * native app" dialog the first time a ported native window opens
8 * and never bother the user again afterwards.
9 *
10 * Today the surface is the native Posts window. The same key is
11 * intentionally generic — any future ported native app (Pages,
12 * Comments, Users, Plugins, …) registers its own slug and reuses
13 * this storage. OS Settings → Features exposes a "Reset what's-new
14 * dialogs" button that clears the whole list so the user can see
15 * every intro again from scratch.
16 *
17 * Storage shape:
18 * user meta `desktop_mode_seen_intros` → array<string> of slugs.
19 * `[ 'posts' ]`, `[ 'posts', 'pages' ]`, etc. Slug values pass
20 * through `sanitize_key()` and the list is capped at 64 entries
21 * so a runaway client cannot bloat user-meta indefinitely.
22 *
23 * @package WPDesktopMode
24 * @since 0.8.0
25 */
26
27 defined( 'ABSPATH' ) || exit;
28
29 /** User meta key — see file header for shape. */
30 const DESKTOP_MODE_SEEN_INTROS_META_KEY = 'desktop_mode_seen_intros';
31
32 /** Hard cap so a malicious client cannot grow the list unbounded. */
33 const DESKTOP_MODE_SEEN_INTROS_MAX = 64;
34
35 /**
36 * Returns the list of intro slugs the user has dismissed.
37 *
38 * @since 0.8.0
39 *
40 * @param int $user_id User ID.
41 * @return string[] Sanitized list (may be empty).
42 */
43 function desktop_mode_get_seen_intros( $user_id ) {
44 $user_id = (int) $user_id;
45 if ( $user_id <= 0 ) {
46 return array();
47 }
48
49 $raw = get_user_meta( $user_id, DESKTOP_MODE_SEEN_INTROS_META_KEY, true );
50 if ( ! is_array( $raw ) ) {
51 return array();
52 }
53
54 return desktop_mode_sanitize_seen_intros( $raw );
55 }
56
57 /**
58 * Whether the user has already dismissed the given intro.
59 *
60 * @since 0.8.0
61 *
62 * @param int $user_id User ID.
63 * @param string $slug Intro slug (e.g. `'posts'`).
64 * @return bool
65 */
66 function desktop_mode_has_seen_intro( $user_id, $slug ) {
67 $slug = sanitize_key( (string) $slug );
68 if ( '' === $slug ) {
69 return false;
70 }
71 return in_array( $slug, desktop_mode_get_seen_intros( $user_id ), true );
72 }
73
74 /**
75 * Adds a slug to the user's seen-intros list.
76 *
77 * Idempotent — re-marking an already-seen intro is a no-op that
78 * still returns true.
79 *
80 * @since 0.8.0
81 *
82 * @param int $user_id User ID.
83 * @param string $slug Intro slug.
84 * @return bool True on successful write (or no-op), false otherwise.
85 */
86 function desktop_mode_mark_intro_seen( $user_id, $slug ) {
87 $user_id = (int) $user_id;
88 $slug = sanitize_key( (string) $slug );
89 if ( $user_id <= 0 || '' === $slug ) {
90 return false;
91 }
92
93 $current = desktop_mode_get_seen_intros( $user_id );
94 if ( in_array( $slug, $current, true ) ) {
95 return true;
96 }
97
98 $current[] = $slug;
99 $current = array_slice( $current, 0, DESKTOP_MODE_SEEN_INTROS_MAX );
100
101 return false !== update_user_meta(
102 $user_id,
103 DESKTOP_MODE_SEEN_INTROS_META_KEY,
104 $current
105 );
106 }
107
108 /**
109 * Wipes every seen-intro entry for the user. Used by the OS
110 * Settings → Features "Reset what's-new dialogs" button.
111 *
112 * @since 0.8.0
113 *
114 * @param int $user_id User ID.
115 * @return bool True on success.
116 */
117 function desktop_mode_clear_seen_intros( $user_id ) {
118 $user_id = (int) $user_id;
119 if ( $user_id <= 0 ) {
120 return false;
121 }
122 return (bool) delete_user_meta( $user_id, DESKTOP_MODE_SEEN_INTROS_META_KEY );
123 }
124
125 /**
126 * Coerces a raw payload to a clean list of slugs.
127 *
128 * @since 0.8.0
129 *
130 * @param mixed $raw Raw value.
131 * @return string[]
132 */
133 function desktop_mode_sanitize_seen_intros( $raw ) {
134 if ( ! is_array( $raw ) ) {
135 return array();
136 }
137 $out = array();
138 foreach ( $raw as $entry ) {
139 if ( ! is_string( $entry ) ) {
140 continue;
141 }
142 $slug = sanitize_key( $entry );
143 if ( '' === $slug ) {
144 continue;
145 }
146 $out[] = $slug;
147 }
148 return array_slice( array_values( array_unique( $out ) ), 0, DESKTOP_MODE_SEEN_INTROS_MAX );
149 }
150
151 /**
152 * Registers REST routes for the seen-intros surface.
153 *
154 * Routes:
155 * POST /desktop-mode/v1/intros/seen body: { slug: string }
156 * DELETE /desktop-mode/v1/intros no body — clears the list
157 *
158 * Both return the post-mutation list so the client can refresh its
159 * local snapshot without a follow-up GET.
160 *
161 * @since 0.8.0
162 */
163 function desktop_mode_register_seen_intros_routes() {
164 register_rest_route(
165 'desktop-mode/v1',
166 '/intros/seen',
167 array(
168 'methods' => WP_REST_Server::CREATABLE,
169 'callback' => 'desktop_mode_rest_mark_intro_seen',
170 'permission_callback' => 'desktop_mode_rest_seen_intros_permission',
171 'args' => array(
172 'slug' => array(
173 'required' => true,
174 'type' => 'string',
175 ),
176 ),
177 )
178 );
179
180 register_rest_route(
181 'desktop-mode/v1',
182 '/intros',
183 array(
184 'methods' => WP_REST_Server::DELETABLE,
185 'callback' => 'desktop_mode_rest_clear_seen_intros',
186 'permission_callback' => 'desktop_mode_rest_seen_intros_permission',
187 )
188 );
189 }
190 add_action( 'rest_api_init', 'desktop_mode_register_seen_intros_routes' );
191
192 /**
193 * Permission gate — any logged-in user may manage their own seen-
194 * intros list.
195 *
196 * @since 0.8.0
197 *
198 * @return bool
199 */
200 function desktop_mode_rest_seen_intros_permission() {
201 return is_user_logged_in() && current_user_can( 'read' );
202 }
203
204 /**
205 * REST handler for `POST /desktop-mode/v1/intros/seen`.
206 *
207 * @since 0.8.0
208 *
209 * @param WP_REST_Request $request REST request.
210 * @return WP_REST_Response|WP_Error
211 */
212 function desktop_mode_rest_mark_intro_seen( WP_REST_Request $request ) {
213 $user_id = get_current_user_id();
214 $slug = sanitize_key( (string) $request->get_param( 'slug' ) );
215 if ( '' === $slug ) {
216 return new WP_Error(
217 'desktop_mode_invalid_intro_slug',
218 __( 'The `slug` parameter must be a non-empty string.', 'desktop-mode' ),
219 array( 'status' => 400 )
220 );
221 }
222 desktop_mode_mark_intro_seen( $user_id, $slug );
223 return rest_ensure_response(
224 array( 'seenIntros' => desktop_mode_get_seen_intros( $user_id ) )
225 );
226 }
227
228 /**
229 * REST handler for `DELETE /desktop-mode/v1/intros`.
230 *
231 * @since 0.8.0
232 *
233 * @return WP_REST_Response
234 */
235 function desktop_mode_rest_clear_seen_intros() {
236 $user_id = get_current_user_id();
237 desktop_mode_clear_seen_intros( $user_id );
238 return rest_ensure_response(
239 array( 'seenIntros' => desktop_mode_get_seen_intros( $user_id ) )
240 );
241 }
242