Repository navigation
Expand file tree
/
Copy pathclass-user-picker.php
More file actions
220 lines (191 loc) · 5.59 KB
/
Copy pathclass-user-picker.php
File metadata and controls
220 lines (191 loc) · 5.59 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
<?php
/**
* Server-side backend for every "pick a user" control.
*
* @package WP_Stream
*/
namespace WP_Stream;
/**
* Class User_Picker
*
* One shared decision for the records filter, settings exclude rules, and the
* alert author trigger: under the preload cap (Admin::get_preload_users_max(),
* filterable) every user is preloaded into a native <select>; over the cap the
* control becomes the Ajax combobox (src/js/utils/user-combobox.js) whose
* search hits Admin_Ajax::ajax_filters() -> User_Picker::search().
*
* The option source is wp_users (+ network super-admins on multisite, plus the
* WP-CLI pseudo-user id 0) — the same set the previous Select2 Ajax dropdowns
* offered. Labels come from Author; 0 renders as "WP-CLI" and deleted users as
* "Unknown user {id}" so stored values always display.
*
* Stateless: no Plugin dependency, safe to instantiate anywhere.
*
* @package WP_Stream
*/
class User_Picker {
/**
* Picker state for the current preload cap.
*
* Under the cap every user (plus network super-admins on multisite and the
* WP-CLI pseudo-user) is preloaded into the picker, sorted by display name.
* Over the cap the picker switches to Ajax search and no items are sent.
*
* @param int $cap Preload cap (see Admin::get_preload_users_max()).
* @return array{ajax: bool, items: array<int, array{id: int, text: string, label: string, icon: string|false}>}
*/
public function get( int $cap ): array {
if ( $cap < 1 ) {
return array(
'ajax' => true,
'items' => array(),
);
}
$user_count = count_users();
$total_users = $user_count['total_users'];
if ( $total_users > $cap ) {
return array(
'ajax' => true,
'items' => array(),
);
}
return array(
'ajax' => false,
'items' => $this->label_items( $this->user_ids() ),
);
}
/**
* Ajax search results for a user picker.
*
* Matches logins, names, emails, and URLs (so network super-admins surface
* when their own fields match), and offers the WP-CLI pseudo-user for
* system-ish searches.
*
* @param string $term Search term.
* @param int $limit Maximum rows to return.
* @return array<int, array{id: int, text: string, label: string, icon: string|false}>
*/
public function search( string $term, int $limit ): array {
$term = trim( $term );
$limit = max( 1, $limit );
if ( '' === $term ) {
return array();
}
$query = new \WP_User_Query(
array(
'search' => '*' . $term . '*',
'search_columns' => array(
'user_login',
'user_nicename',
'user_email',
'user_url',
'display_name',
),
'orderby' => 'display_name',
'number' => $limit,
'fields' => 'ID',
'count_total' => false,
)
);
$ids = array_map( 'intval', $query->get_results() );
// Note: super-admins are NOT appended unconditionally (the previous
// exclude-screen endpoint did that); the search itself covers logins,
// emails, URLs, and display names, so they surface when relevant.
if ( preg_match( '/wp|cli|system|unknown/i', $term ) ) {
$ids[] = 0;
}
return array_slice( $this->label_items( array_values( array_unique( $ids ) ) ), 0, $limit );
}
/**
* Display label for a picker user.
*
* @param int $user_id User ID. Zero is WP-CLI.
* @return string
*/
public function label( int $user_id ): string {
if ( 0 === $user_id ) {
return 'WP-CLI';
}
$author = new Author( $user_id );
if ( $author->is_deleted() ) {
return sprintf(
/* translators: %d: user ID */
__( 'Unknown user %d', 'stream' ),
$user_id
);
}
return $author->get_display_name();
}
/**
* Display label for a stored picker value (form input, alert meta, query arg).
*
* @param mixed $value Raw stored value.
* @return string Label, or empty string when the value is not a user ID.
*/
public function label_for_value( $value ): string {
$value = (string) $value;
return ctype_digit( $value ) ? $this->label( (int) $value ) : '';
}
/**
* Avatar URL for a picker user.
*
* @param int $user_id User ID. Zero is WP-CLI.
* @return string|false Image URL, or false when avatars are disabled or missing.
*/
public function icon( int $user_id ) {
return ( new Author( $user_id ) )->get_avatar_src( 32 );
}
/**
* All user IDs offered by a picker: site users, network super-admins, WP-CLI.
*
* @return int[]
*/
private function user_ids(): array {
$ids = array_map( 'intval', get_users( array( 'fields' => 'ID' ) ) );
if ( is_multisite() && is_super_admin() ) {
foreach ( get_super_admins() as $login ) {
$super = get_user_by( 'login', $login );
if ( $super ) {
$ids[] = (int) $super->ID;
}
}
}
$ids[] = 0; // WP-CLI pseudo-user.
return array_values( array_unique( $ids ) );
}
/**
* Attach labels, icons, and natural name order to picker user IDs.
*
* @param int[] $ids User IDs (0 is WP-CLI).
* @return array<int, array{id: int, text: string, label: string, icon: string|false}>
*/
private function label_items( array $ids ): array {
$lookup = array();
foreach ( $ids as $user_id ) {
if ( $user_id > 0 ) {
$lookup[] = $user_id;
}
}
if ( array() !== $lookup && function_exists( 'cache_users' ) ) {
cache_users( $lookup );
}
$items = array();
foreach ( $ids as $user_id ) {
$label = $this->label( $user_id );
$icon = $this->icon( $user_id );
$items[] = array(
'id' => $user_id,
'text' => $label,
'label' => $label,
'icon' => $icon,
);
}
usort(
$items,
static function ( $a, $b ) {
return strnatcasecmp( $a['label'], $b['label'] );
}
);
return $items;
}
}