PluginProbe
OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin / 1.1.7
OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin v1.1.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 / apps / users / users.os.php

users.os.php in OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin 1.1.7, at apps/users/users.os.php

292 lines 10.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Users — the Users window, as an OpenStation app.
4 *
5 * Claims the FROZEN id `desktop-mode-users` (see AGENTS.md) so the
6 * `users.php` URL remap, session restores and every hook keep
7 * working. The body is `users.os.ts`, a client view over the rows
8 * `data()` reads from `wp/v2/users` in-process — the same collection,
9 * fields and filterable query, plus a page of content counts in two
10 * grouped queries. The mutations are actions over the functions in
11 * `parts/rest.php`, which the `desktop-mode/v1/users*` routes expose
12 * too. The Profile tab hosts `<os-user-profile>` from the companion
13 * bundle `parts/profile-script.php` registers.
14 *
15 * (Header kept short on purpose: Plugin Check's direct-access scan
16 * reads only the first 50 raw lines, and the guard below must land
17 * inside that window.)
18 *
19 * @package OpenStation
20 */
21
22 namespace OpenStation\Apps\Users;
23
24 use OpenStation\App;
25 use OpenStation\App\Os;
26 use OpenStation\App\State;
27
28 // Direct access, unless a standalone host is booting on bare PHP.
29 if ( ! defined( 'ABSPATH' ) ) {
30 defined( 'OPENSTATION_STANDALONE' ) || exit;
31 }
32
33 require_once __DIR__ . '/parts/permissions.php';
34 require_once __DIR__ . '/parts/login-tracker.php';
35 require_once __DIR__ . '/parts/color-schemes.php';
36 require_once __DIR__ . '/parts/fields.php';
37 require_once __DIR__ . '/parts/facts.php';
38 require_once __DIR__ . '/parts/rest.php';
39 require_once __DIR__ . '/parts/profile-script.php';
40
41 /** The `orderby` values `wp/v2/users` accepts that the table offers. */
42 const SORT_KEYS = array( 'name', 'registered_date', 'email' );
43
44 /**
45 * The query the list reads — the filterable defaults plus the state.
46 *
47 * @param State $state State.
48 * @return array<string,mixed>
49 */
50 function list_query( State $state ) {
51 $query = openstation_users_window_default_query_args();
52 $query['page'] = max( 1, (int) $state->get( 'page' ) );
53 $query['per_page'] = max( 1, (int) $state->get( 'perPage' ) );
54 $query['orderby'] = (string) $state->get( 'orderby' );
55 $query['order'] = (string) $state->get( 'order' );
56 $search = trim( (string) $state->get( 'search' ) );
57 if ( '' !== $search ) {
58 $query['search'] = $search;
59 }
60 return $query;
61 }
62
63 /**
64 * Say what a mutation did.
65 *
66 * @param Os $os Host handle.
67 * @param array|\WP_Error $result The mutation's answer.
68 * @param callable $ok `function ( array $result ): string` — the success message.
69 * @return bool Whether it succeeded.
70 */
71 function report( Os $os, $result, callable $ok ) {
72 if ( is_wp_error( $result ) ) {
73 $os->toast( $result->get_error_message() );
74 return false;
75 }
76 $os->toast( $ok( $result ) );
77 return true;
78 }
79
80 /**
81 * How many of a bulk result's rows succeeded.
82 *
83 * @param array<string,mixed> $result Bulk result.
84 * @return int
85 */
86 function ok_count( array $result ) {
87 return count(
88 array_filter(
89 (array) $result['results'],
90 static function ( $row ) {
91 return ! empty( $row['ok'] );
92 }
93 )
94 );
95 }
96
97 return App::define( 'desktop-mode-users' )
98 ->title( __( 'Users', 'desktop-mode' ) )
99 ->icon( 'dashicons-admin-users' )
100 ->size( 1100, 720 )
101 ->min_size( 720, 480 )
102 // The Users dock tile lives in WordPress's `$menu`; the URL remap
103 // routes its click here when the opt-in is on.
104 ->placement( 'none' )
105 ->can(
106 static function () {
107 return openstation_users_window_user_can_register();
108 }
109 )
110 // Resolved when the window registers, for the viewer registering it.
111 ->config( 'openstation_users_profile_facts' )
112 ->state(
113 array(
114 'page' => 1,
115 'perPage' => 20,
116 'search' => '',
117 // The presence filter (All / Online / Active 30d / Never
118 // logged in) — a client-side slice of the page.
119 'status' => '',
120 'orderby' => 'name',
121 'order' => 'asc',
122 // The tab strip: `all` | `add-new` | `edit`.
123 'tab' => 'all',
124 // The Add User form's last failure, for the field it names.
125 'createError' => '',
126 'createField' => '',
127 // Bumped on every successful create — the form resets on it.
128 'created' => 0,
129 )
130 )
131 // A query change replaces the result set — back to the first page.
132 ->action(
133 'filter',
134 static function ( State $state ) {
135 $state->set( 'page', 1 );
136 }
137 )
138 ->action(
139 'page',
140 static function ( State $state, Os $os, array $args ) {
141 $state->set( 'page', max( 1, (int) ( $args['page'] ?? 1 ) ) );
142 }
143 )
144 // A column header click; the table's keys map to the collection's.
145 ->action(
146 'sort',
147 static function ( State $state, Os $os, array $args ) {
148 $orderby = sanitize_key( (string) ( $args['orderby'] ?? 'name' ) );
149 $state->set( 'orderby', in_array( $orderby, SORT_KEYS, true ) ? $orderby : 'name' );
150 $state->set( 'order', 'desc' === strtolower( (string) ( $args['order'] ?? 'asc' ) ) ? 'desc' : 'asc' );
151 }
152 )
153 ->action(
154 'bulk-role',
155 static function ( State $state, Os $os, array $args ) {
156 if ( ! $os->can( 'promote_users' ) ) {
157 $os->toast( __( 'You are not allowed to change roles.', 'desktop-mode' ) );
158 return;
159 }
160 $ids = openstation_users_window_clean_ids( $args['ids'] ?? array() );
161 $result = openstation_users_window_apply_bulk_role( $ids, (string) ( $args['role'] ?? '' ) );
162 $done = report(
163 $os,
164 $result,
165 static function ( array $result ) use ( $ids ) {
166 $ok = ok_count( $result );
167 if ( 0 === $ok ) {
168 return __( 'No users updated.', 'desktop-mode' );
169 }
170 // translators: %1$d users updated, %2$d failed.
171 return sprintf( __( 'Role updated for %1$d user(s) (%2$d skipped).', 'desktop-mode' ), $ok, count( $ids ) - $ok );
172 }
173 );
174 if ( $done ) {
175 $os->announce( 'user', 'updated', $ids );
176 }
177 }
178 )
179 ->action(
180 'bulk-delete',
181 static function ( State $state, Os $os, array $args ) {
182 if ( ! $os->can( is_multisite() ? 'remove_users' : 'delete_users' ) ) {
183 $os->toast( __( 'You are not allowed to delete users.', 'desktop-mode' ) );
184 return;
185 }
186 $ids = openstation_users_window_clean_ids( $args['ids'] ?? array() );
187 $result = openstation_users_window_apply_bulk_delete( $ids, (int) ( $args['reassign'] ?? 0 ) );
188 $done = report(
189 $os,
190 $result,
191 static function ( array $result ) use ( $ids ) {
192 $ok = ok_count( $result );
193 // translators: %1$d users deleted, %2$d skipped.
194 return sprintf( __( '%1$d user(s) deleted (%2$d skipped).', 'desktop-mode' ), $ok, count( $ids ) - $ok );
195 }
196 );
197 if ( $done ) {
198 $os->announce( 'user', 'deleted', $ids );
199 }
200 }
201 )
202 ->action(
203 'send-reset',
204 static function ( State $state, Os $os, array $args ) {
205 report(
206 $os,
207 $os->can( 'edit_users' )
208 ? openstation_users_window_send_password_reset( (int) ( $args['id'] ?? 0 ) )
209 : new \WP_Error( 'openstation_users_forbidden', __( 'You are not allowed to email this user.', 'desktop-mode' ) ),
210 static function ( array $result ) {
211 // translators: %s is the user's email address.
212 return sprintf( __( 'Reset email sent to %s.', 'desktop-mode' ), $result['email'] );
213 }
214 );
215 }
216 )
217 ->action(
218 'resend-welcome',
219 static function ( State $state, Os $os, array $args ) {
220 report(
221 $os,
222 $os->can( 'edit_users' )
223 ? openstation_users_window_resend_welcome( (int) ( $args['id'] ?? 0 ) )
224 : new \WP_Error( 'openstation_users_forbidden', __( 'You are not allowed to email this user.', 'desktop-mode' ) ),
225 static function ( array $result ) {
226 // translators: %s is the user's email address.
227 return sprintf( __( 'Welcome email resent to %s.', 'desktop-mode' ), $result['email'] );
228 }
229 );
230 }
231 )
232 ->action(
233 'create',
234 static function ( State $state, Os $os, array $args ) {
235 $state->set( 'createError', '' );
236 $state->set( 'createField', '' );
237 if ( ! $os->can( 'create_users' ) ) {
238 $state->set( 'createError', __( 'You are not allowed to create users.', 'desktop-mode' ) );
239 return;
240 }
241 $values = is_array( $args['values'] ?? null ) ? $args['values'] : $args;
242 $result = openstation_users_window_create_user( $values );
243 if ( is_wp_error( $result ) ) {
244 $code = $result->get_error_code();
245 $field = '';
246 if ( in_array( $code, array( 'openstation_users_username_exists', 'existing_user_login', 'openstation_users_username_invalid', 'openstation_users_username_required' ), true ) ) {
247 $field = 'username';
248 } elseif ( in_array( $code, array( 'openstation_users_email_exists', 'existing_user_email', 'openstation_users_email_invalid' ), true ) ) {
249 $field = 'email';
250 } elseif ( 'openstation_users_role_forbidden' === $code ) {
251 $field = 'role';
252 }
253 $state->set( 'createError', $result->get_error_message() );
254 $state->set( 'createField', $field );
255 $os->toast( $result->get_error_message() );
256 return;
257 }
258 // translators: %s is the user's email address.
259 $os->toast( sprintf( __( 'User created — welcome email sent to %s.', 'desktop-mode' ), $result['email'] ) );
260 $os->announce( 'user', 'created', array( (int) $result['user_id'] ) );
261 // Back to the list, first page, so the new user shows up.
262 $state->set( 'tab', 'all' );
263 $state->set( 'page', 1 );
264 $state->set( 'created', (int) $state->get( 'created' ) + 1 );
265 }
266 )
267 // A profile saved in the User Edit window, a role changed here, a
268 // user created anywhere: the list repaints (the app's own announces
269 // are skipped by the runtime — the action already returned the rows).
270 ->watch( 'user' )
271 ->data(
272 static function ( State $state ) {
273 $list = openstation_app_rest_page( 'wp/v2/users', list_query( $state ) );
274 // Page out of range — the user was on page 7 and changed the
275 // page size. Land on page 1 rather than paint an empty table.
276 if ( $state->get( 'page' ) > 1 && openstation_app_rest_page_is_out_of_range( $list ) ) {
277 $state->set( 'page', 1 );
278 $list = openstation_app_rest_page( 'wp/v2/users', list_query( $state ) );
279 }
280 // The Content column: the page's counts in two grouped
281 // queries, merged in under the name the REST field uses.
282 $stats = openstation_users_window_stats_for( wp_list_pluck( $list['items'], 'id' ) );
283 foreach ( $list['items'] as $i => $row ) {
284 $id = isset( $row['id'] ) ? (int) $row['id'] : 0;
285 if ( isset( $stats[ $id ] ) ) {
286 $list['items'][ $i ]['openstation_user_stats'] = $stats[ $id ];
287 }
288 }
289 return array( 'list' => $list );
290 }
291 );
292