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 / cards.php

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

366 lines 11.6 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 cards.
4 *
5 * Third-party plugins register small, structured cards here instead of
6 * injecting arbitrary markup into Station Home. Cards are user-controlled:
7 * each declaration chooses an initial state and every user can subsequently
8 * opt in or out without changing the plugin's site-wide configuration.
9 *
10 * @package OpenStation
11 */
12
13 defined( 'ABSPATH' ) || exit;
14
15 /** Per-user map of explicit Station Home card choices (`card-id => bool`). */
16 const OPENSTATION_STATION_HOME_CARD_PREFERENCES_META = 'openstation_station_home_card_preferences';
17
18 /**
19 * Register a card contributed by a plugin to Station Home.
20 *
21 * The callback runs only when the current user has enabled the card. It
22 * returns structured, plain-text data so the Station Home renderer keeps
23 * ownership of accessibility, layout, responsive behavior, and theming.
24 *
25 * Example:
26 *
27 * ```php
28 * openstation_register_station_home_card( 'my-plugin-orders', array(
29 * 'label' => __( 'Orders', 'my-plugin' ),
30 * 'description' => __( 'Orders waiting to be fulfilled.', 'my-plugin' ),
31 * 'provider' => __( 'My Plugin', 'my-plugin' ),
32 * 'icon' => 'dashicons-cart',
33 * 'default_enabled' => false,
34 * 'capabilities' => array( 'manage_options' ),
35 * 'callback' => function () {
36 * return array(
37 * 'value' => '4',
38 * 'detail' => __( 'Ready to fulfil', 'my-plugin' ),
39 * 'url' => admin_url( 'admin.php?page=my-plugin-orders' ),
40 * 'action_label' => __( 'Open orders', 'my-plugin' ),
41 * 'tone' => 'warning',
42 * );
43 * },
44 * ) );
45 * ```
46 *
47 * @param string $id Unique kebab-case card id.
48 * @param array $args {
49 * Card registration options.
50 *
51 * @type string $label Human-readable card name. Required.
52 * @type string $description Explanation shown in the card picker.
53 * @type string $provider Plugin/provider name shown on the card.
54 * @type string $icon Dashicon class or safe image URL.
55 * @type callable $callback Returns the current card data. Required.
56 * @type bool $default_enabled Initial state until the user chooses.
57 * @type int $order Sort order within the contributed area.
58 * @type string[] $capabilities Gate: ALL capabilities must match.
59 * }
60 * @return true|WP_Error `true` on success; `WP_Error` otherwise.
61 */
62 function openstation_register_station_home_card( $id, $args = array() ) {
63 $raw_id = (string) $id;
64 $id = sanitize_key( $raw_id );
65 if ( '' === $id || $raw_id !== $id ) {
66 return openstation_registration_error(
67 'openstation_invalid_station_home_card_id',
68 __( 'Station Home card id is required and must be a valid slug.', 'desktop-mode' )
69 );
70 }
71
72 $args = wp_parse_args(
73 $args,
74 array(
75 'label' => '',
76 'description' => '',
77 'provider' => '',
78 'icon' => 'dashicons-admin-plugins',
79 'callback' => null,
80 'default_enabled' => false,
81 'order' => 10,
82 'capabilities' => array(),
83 )
84 );
85
86 foreach ( (array) $args['capabilities'] as $capability ) {
87 if ( ! current_user_can( (string) $capability ) ) {
88 return openstation_registration_error(
89 'openstation_capability_denied',
90 sprintf(
91 /* translators: %s: capability slug. */
92 __( 'Current user lacks the %s capability required to register this Station Home card.', 'desktop-mode' ),
93 (string) $capability
94 ),
95 array(
96 'capability' => (string) $capability,
97 'id' => $id,
98 )
99 );
100 }
101 }
102
103 $label = sanitize_text_field( (string) $args['label'] );
104 if ( '' === $label ) {
105 return openstation_registration_error(
106 'openstation_missing_label',
107 __( 'Station Home card registration requires a non-empty `label`.', 'desktop-mode' ),
108 array( 'id' => $id )
109 );
110 }
111 if ( ! is_callable( $args['callback'] ) ) {
112 return openstation_registration_error(
113 'openstation_invalid_callback',
114 __( 'Station Home card registration requires a callable `callback`.', 'desktop-mode' ),
115 array( 'id' => $id )
116 );
117 }
118
119 $entry = array(
120 'id' => $id,
121 'label' => $label,
122 'description' => sanitize_textarea_field( (string) $args['description'] ),
123 'provider' => sanitize_text_field( (string) $args['provider'] ),
124 'icon' => openstation_sanitize_dock_icon( (string) $args['icon'] ),
125 'callback' => $args['callback'],
126 'default_enabled' => (bool) $args['default_enabled'],
127 'order' => (int) $args['order'],
128 );
129 openstation_station_home_card_registry( $id, $entry );
130
131 /**
132 * Fires after a Station Home card is successfully registered.
133 *
134 * @param string $id Card id.
135 * @param array $entry Stored registry entry.
136 */
137 do_action( 'openstation_station_home_card_registered', $id, $entry );
138
139 return true;
140 }
141
142 /**
143 * Internal Station Home card registry.
144 *
145 * @internal
146 *
147 * @param string $id Card id, empty for all, or `__flush__` for tests.
148 * @param array|null $entry Entry to write.
149 * @return array|null
150 */
151 function openstation_station_home_card_registry( $id = '', $entry = null ) {
152 static $registry = null;
153 if ( null === $registry ) {
154 $registry = openstation_create_registry();
155 }
156 return $registry( $id, $entry );
157 }
158
159 /**
160 * Remove a Station Home card registration.
161 *
162 * @param string $id Card id.
163 * @return bool Whether a card was removed.
164 */
165 function openstation_unregister_station_home_card( $id ) {
166 $id = sanitize_key( (string) $id );
167 if ( '' === $id || null === openstation_station_home_card_registry( $id ) ) {
168 return false;
169 }
170
171 $cards = openstation_station_home_card_registry();
172 unset( $cards[ $id ] );
173 openstation_station_home_card_registry( '__flush__' );
174 foreach ( $cards as $card_id => $entry ) {
175 openstation_station_home_card_registry( $card_id, $entry );
176 }
177 return true;
178 }
179
180 /**
181 * Return the post-filter card registry in deterministic display order.
182 *
183 * @return array[] Entries keyed by card id.
184 */
185 function openstation_station_home_get_registered_cards() {
186 $cards = openstation_station_home_card_registry();
187
188 /**
189 * Filters the registered Station Home cards for the current user.
190 *
191 * Plugins may add, remove, or replace entries. Added entries must use the
192 * same shape accepted by `openstation_register_station_home_card()`.
193 *
194 * @param array[] $cards Entries keyed by card id.
195 * @param int $user_id Current user id.
196 */
197 $cards = apply_filters( 'openstation_station_home_cards', $cards, get_current_user_id() );
198 if ( ! is_array( $cards ) ) {
199 return array();
200 }
201
202 $normalized = array();
203 foreach ( $cards as $key => $entry ) {
204 if ( ! is_array( $entry ) || ! is_callable( $entry['callback'] ?? null ) ) {
205 continue;
206 }
207 $allowed = true;
208 foreach ( (array) ( $entry['capabilities'] ?? array() ) as $capability ) {
209 if ( ! current_user_can( (string) $capability ) ) {
210 $allowed = false;
211 break;
212 }
213 }
214 if ( ! $allowed ) {
215 continue;
216 }
217 $id = sanitize_key( (string) ( $entry['id'] ?? $key ) );
218 $label = sanitize_text_field( (string) ( $entry['label'] ?? '' ) );
219 if ( '' === $id || '' === $label ) {
220 continue;
221 }
222 $normalized[ $id ] = array(
223 'id' => $id,
224 'label' => $label,
225 'description' => sanitize_textarea_field( (string) ( $entry['description'] ?? '' ) ),
226 'provider' => sanitize_text_field( (string) ( $entry['provider'] ?? '' ) ),
227 'icon' => openstation_sanitize_dock_icon( (string) ( $entry['icon'] ?? 'dashicons-admin-plugins' ) ),
228 'callback' => $entry['callback'],
229 'default_enabled' => (bool) ( $entry['default_enabled'] ?? false ),
230 'order' => (int) ( $entry['order'] ?? 10 ),
231 );
232 }
233
234 uasort(
235 $normalized,
236 static function ( $left, $right ) {
237 $order = $left['order'] <=> $right['order'];
238 return 0 !== $order ? $order : strcasecmp( $left['label'], $right['label'] );
239 }
240 );
241
242 return $normalized;
243 }
244
245 /**
246 * Read a user's explicit Station Home card choices.
247 *
248 * @param int $user_id User id. Defaults to the current user.
249 * @return array<string, bool>
250 */
251 function openstation_station_home_get_card_preferences( $user_id = 0 ) {
252 $user_id = $user_id > 0 ? (int) $user_id : get_current_user_id();
253 $stored = get_user_meta( $user_id, OPENSTATION_STATION_HOME_CARD_PREFERENCES_META, true );
254 if ( ! is_array( $stored ) ) {
255 return array();
256 }
257
258 $preferences = array();
259 foreach ( $stored as $id => $enabled ) {
260 $id = sanitize_key( (string) $id );
261 if ( '' !== $id ) {
262 $preferences[ $id ] = (bool) $enabled;
263 }
264 }
265 return $preferences;
266 }
267
268 /**
269 * Resolve a card's effective per-user enabled state.
270 *
271 * @param string $id Card id.
272 * @param array $entry Registry entry.
273 * @param array $preferences Explicit preference map.
274 * @return bool
275 */
276 function openstation_station_home_card_is_enabled( $id, $entry, $preferences ) {
277 if ( array_key_exists( $id, $preferences ) ) {
278 return (bool) $preferences[ $id ];
279 }
280 return (bool) $entry['default_enabled'];
281 }
282
283 /**
284 * Build public preference rows and enabled card payloads for the snapshot.
285 *
286 * @param array[] $cards Registered card entries.
287 * @param array<string, bool> $preferences Explicit user preferences.
288 * @return array{cards: array[], preferences: array[]}
289 */
290 function openstation_station_home_build_cards( $cards, $preferences ) {
291 $payload = array();
292 $preference_rows = array();
293 $user_id = get_current_user_id();
294
295 foreach ( $cards as $id => $entry ) {
296 $enabled = openstation_station_home_card_is_enabled( $id, $entry, $preferences );
297 $preference_rows[] = array(
298 'id' => $id,
299 'label' => $entry['label'],
300 'description' => $entry['description'],
301 'provider' => $entry['provider'],
302 'icon' => $entry['icon'],
303 'enabled' => $enabled,
304 'defaultEnabled' => (bool) $entry['default_enabled'],
305 );
306
307 if ( ! $enabled ) {
308 continue;
309 }
310
311 try {
312 $data = call_user_func( $entry['callback'], $user_id, $entry );
313 } catch ( Throwable $error ) {
314 /**
315 * Fires when a contributed card callback throws.
316 *
317 * @param Throwable $error The callback error.
318 * @param string $id Card id.
319 * @param array $entry Registry entry.
320 */
321 do_action( 'openstation_station_home_card_error', $error, $id, $entry );
322 continue;
323 }
324 if ( is_wp_error( $data ) || ! is_array( $data ) ) {
325 continue;
326 }
327
328 /**
329 * Filters an enabled card's dynamic data before sanitization.
330 *
331 * @param array $data Callback data.
332 * @param string $id Card id.
333 * @param array $entry Registry entry.
334 * @param int $user_id Current user id.
335 */
336 $data = apply_filters( 'openstation_station_home_card_data', $data, $id, $entry, $user_id );
337 if ( ! is_array( $data ) ) {
338 continue;
339 }
340
341 $tone = (string) ( $data['tone'] ?? 'neutral' );
342 if ( ! in_array( $tone, array( 'neutral', 'info', 'success', 'warning', 'danger' ), true ) ) {
343 $tone = 'neutral';
344 }
345
346 $payload[] = array(
347 'id' => $id,
348 'label' => $entry['label'],
349 'description' => $entry['description'],
350 'provider' => $entry['provider'],
351 'icon' => $entry['icon'],
352 'value' => sanitize_text_field( (string) ( $data['value'] ?? '' ) ),
353 'detail' => sanitize_textarea_field( (string) ( $data['detail'] ?? '' ) ),
354 'url' => esc_url_raw( (string) ( $data['url'] ?? '' ) ),
355 'actionLabel' => sanitize_text_field( (string) ( $data['action_label'] ?? '' ) ),
356 'external' => (bool) ( $data['external'] ?? false ),
357 'tone' => $tone,
358 );
359 }
360
361 return array(
362 'cards' => $payload,
363 'preferences' => $preference_rows,
364 );
365 }
366