PluginProbe ʕ •ᴥ•ʔ
Advanced Custom Fields (ACF®) / 6.8.9
Advanced Custom Fields (ACF®) v6.8.9
6.8.9 6.8.8 6.8.7 6.8.6 6.8.5 6.8.4 6.8.3 6.8.2 6.8.1 5.8.5 5.8.6 5.8.7 5.8.8 5.8.9 5.9.0 5.9.1 5.9.2 5.9.3 5.9.4 5.9.5 5.9.6 5.9.7 5.9.8 5.9.9 6.0.0 6.0.1 6.0.2 6.0.3 6.0.4 6.0.5 6.0.6 6.0.7 6.1.0 6.1.1 6.1.2 6.1.3 6.1.4 6.1.5 6.1.6 6.1.7 6.1.8 6.2.0 6.2.1 6.2.2 6.2.3 6.2.4 6.2.5 6.2.6 6.2.6.1 6.2.7 6.2.8 6.2.9 6.3.0 6.3.1 6.3.10.2 6.3.11 6.3.12 6.3.2 6.3.3 6.3.4 6.3.5 6.3.6 6.3.6.1 6.4.0 6.4.0.1 6.4.1 6.4.2 6.4.3 6.5.0 6.5.1 6.6.0 6.6.1 6.6.2 6.7.0 6.7.1 6.7.2 6.8.0 trunk 1.0.0 1.0.2 1.0.3 1.0.5 1.1.0 1.1.1 1.1.2 1.1.3 1.1.4 2.0.0 2.0.1 2.0.2 2.0.3 2.0.4 2.0.5 2.1.1 2.1.3 2.1.4 3.0.0 3.0.1 3.0.2 3.0.3 3.0.4 3.0.6 3.0.7 3.1.0 3.1.1 3.1.2 3.1.3 3.1.4 3.1.5 3.1.6 3.1.7 3.1.8 3.1.9 3.2.0 3.2.2 3.2.3 3.2.4 3.2.5 3.2.6 3.2.7 3.2.8 3.2.9 3.3.0 3.3.1 3.3.2 3.3.3 3.3.4 3.3.5 3.3.6 3.3.7 3.3.8 3.3.9 3.4.0 3.4.1 3.4.2 3.4.3 3.5.0 3.5.1 3.5.2 3.5.3 3.5.4 3.5.5 3.5.6 3.5.7 3.5.8 4.0.0 4.0.1 4.0.2 4.0.3 4.1.0 4.1.1 4.1.2 4.1.3 4.1.4 4.1.5 4.1.6 4.1.8 4.2.0 4.2.1 4.2.2 4.3.0 4.3.1 4.3.2 4.3.3 4.3.4 4.3.5 4.3.6 4.3.7 4.3.8 4.3.9 4.4.0 4.4.1 4.4.10 4.4.11 4.4.12 4.4.2 4.4.3 4.4.4 4.4.5 4.4.6 4.4.7 4.4.8 4.4.9 5.10 5.10.1 5.10.2 5.11 5.11.1 5.11.2 5.11.3 5.11.4 5.12 5.12.1 5.12.2 5.12.3 5.12.4 5.12.5 5.12.6 5.6.10 5.6.2 5.6.3 5.6.4 5.6.5 5.6.6 5.6.7 5.6.8 5.6.9 5.7.0 5.7.1 5.7.10 5.7.12 5.7.13 5.7.2 5.7.3 5.7.4 5.7.5 5.7.6 5.7.7 5.7.8 5.7.9 5.8.0 5.8.1 5.8.10 5.8.11 5.8.12 5.8.13 5.8.14 5.8.2 5.8.3 5.8.4
advanced-custom-fields / includes / fields / class-acf-field-user.php
advanced-custom-fields / includes / fields Last commit date
class-acf-field-accordion.php 5 months ago class-acf-field-button-group.php 5 months ago class-acf-field-checkbox.php 2 months ago class-acf-field-color_picker.php 5 months ago class-acf-field-date_picker.php 5 months ago class-acf-field-date_time_picker.php 5 months ago class-acf-field-email.php 5 months ago class-acf-field-file.php 5 months ago class-acf-field-google-map.php 5 months ago class-acf-field-group.php 5 months ago class-acf-field-icon_picker.php 5 months ago class-acf-field-image.php 4 weeks ago class-acf-field-link.php 5 months ago class-acf-field-message.php 5 months ago class-acf-field-number.php 5 months ago class-acf-field-oembed.php 2 months ago class-acf-field-output.php 5 months ago class-acf-field-page_link.php 4 weeks ago class-acf-field-password.php 5 months ago class-acf-field-post_object.php 4 weeks ago class-acf-field-radio.php 2 months ago class-acf-field-range.php 5 months ago class-acf-field-relationship.php 4 weeks ago class-acf-field-select.php 2 months ago class-acf-field-separator.php 5 months ago class-acf-field-tab.php 5 months ago class-acf-field-taxonomy.php 2 months ago class-acf-field-text.php 5 months ago class-acf-field-textarea.php 5 months ago class-acf-field-time_picker.php 5 months ago class-acf-field-true_false.php 5 months ago class-acf-field-url.php 5 months ago class-acf-field-user.php 2 months ago class-acf-field-wysiwyg.php 5 months ago class-acf-field.php 5 months ago index.php 2 years ago
class-acf-field-user.php
758 lines
1 <?php
2 /**
3 * @package ACF
4 * @author WP Engine
5 *
6 * © 2026 Advanced Custom Fields (ACF®). All rights reserved.
7 * "ACF" is a trademark of WP Engine.
8 * Licensed under the GNU General Public License v2 or later.
9 * https://www.gnu.org/licenses/gpl-2.0.html
10 */
11
12 if ( ! class_exists( 'ACF_Field_User' ) ) :
13
14 class ACF_Field_User extends ACF_Field {
15
16 /**
17 * Initializes the field type.
18 *
19 * @date 5/03/2014
20 * @since 5.0.0
21 */
22 function initialize() {
23 $this->name = 'user';
24 $this->label = __( 'User', 'acf' );
25 $this->category = 'relational';
26 $this->description = __( 'Allows the selection of one or more users which can be used to create relationships between data objects.', 'acf' );
27 $this->preview_image = acf_get_url() . '/assets/images/field-type-previews/field-preview-user.png';
28 $this->doc_url = acf_add_url_utm_tags( 'https://www.advancedcustomfields.com/resources/user/', 'docs', 'field-type-selection' );
29 $this->defaults = array(
30 'role' => '',
31 'multiple' => 0,
32 'allow_null' => 0,
33 'return_format' => 'array',
34 'bidirectional_target' => array(),
35 );
36
37 // Register filter variations.
38 acf_add_filter_variations( 'acf/fields/user/query', array( 'name', 'key' ), 1 );
39 acf_add_filter_variations( 'acf/fields/user/result', array( 'name', 'key' ), 2 );
40 acf_add_filter_variations( 'acf/fields/user/search_columns', array( 'name', 'key' ), 3 );
41 add_filter( 'acf/conditional_logic/choices', array( $this, 'render_field_user_conditional_choices' ), 10, 3 );
42
43 // Add AJAX query.
44 add_action( 'wp_ajax_acf/fields/user/query', array( $this, 'ajax_query' ) );
45 add_action( 'wp_ajax_nopriv_acf/fields/user/query', array( $this, 'ajax_query' ) );
46 }
47
48 /**
49 * Filters choices in user conditions.
50 *
51 * @since 6.3
52 *
53 * @param array $choices The selected choice.
54 * @param array $conditional_field The conditional field settings object.
55 * @param string $rule_value The rule value.
56 * @return array
57 */
58 public function render_field_user_conditional_choices( $choices, $conditional_field, $rule_value ) {
59 if ( ! is_array( $conditional_field ) || $conditional_field['type'] !== 'user' ) {
60 return $choices;
61 }
62 if ( ! empty( $rule_value ) ) {
63 $user = acf_get_users(
64 array(
65 'include' => array( $rule_value ),
66 )
67 );
68
69 $user_result = acf_get_user_result( $user[0] );
70 $choices = array( $user_result['id'] => $user_result['text'] );
71 }
72
73 return $choices;
74 }
75
76 /**
77 * Renders the field settings HTML.
78 *
79 * @date 23/01/13
80 * @since 3.6.0
81 *
82 * @param array $field The ACF field.
83 * @return void
84 */
85 function render_field_settings( $field ) {
86 acf_render_field_setting(
87 $field,
88 array(
89 'label' => __( 'Filter by Role', 'acf' ),
90 'instructions' => '',
91 'type' => 'select',
92 'name' => 'role',
93 'choices' => acf_get_user_role_labels(),
94 'multiple' => 1,
95 'ui' => 1,
96 'allow_null' => 1,
97 'placeholder' => __( 'All user roles', 'acf' ),
98 )
99 );
100
101 acf_render_field_setting(
102 $field,
103 array(
104 'label' => __( 'Return Format', 'acf' ),
105 'instructions' => '',
106 'type' => 'radio',
107 'name' => 'return_format',
108 'choices' => array(
109 'array' => __( 'User Array', 'acf' ),
110 'object' => __( 'User Object', 'acf' ),
111 'id' => __( 'User ID', 'acf' ),
112 ),
113 'layout' => 'horizontal',
114 )
115 );
116
117 acf_render_field_setting(
118 $field,
119 array(
120 'label' => __( 'Select Multiple', 'acf' ),
121 'instructions' => 'Allow content editors to select multiple values',
122 'name' => 'multiple',
123 'type' => 'true_false',
124 'ui' => 1,
125 )
126 );
127 }
128
129 /**
130 * Renders the field settings used in the "Validation" tab.
131 *
132 * @since 6.0
133 *
134 * @param array $field The field settings array.
135 * @return void
136 */
137 function render_field_validation_settings( $field ) {
138 acf_render_field_setting(
139 $field,
140 array(
141 'label' => __( 'Allow Null', 'acf' ),
142 'instructions' => '',
143 'name' => 'allow_null',
144 'type' => 'true_false',
145 'ui' => 1,
146 )
147 );
148 }
149
150 /**
151 * Renders the field settings used in the "Advanced" tab.
152 *
153 * @since 6.2
154 *
155 * @param array $field The field settings array.
156 * @return void
157 */
158 public function render_field_advanced_settings( $field ) {
159 acf_render_bidirectional_field_settings( $field );
160 }
161
162 /**
163 * Renders the field input HTML.
164 *
165 * @since 3.6.0
166 *
167 * @param array $field The ACF field.
168 * @return void
169 */
170 public function render_field( $field ) {
171 // Change Field into a select.
172 $field['type'] = 'select';
173 $field['ui'] = 1;
174 $field['ajax'] = 1;
175 $field['choices'] = array();
176 $field['nonce'] = wp_create_nonce( 'acf_field_' . $this->name . '_' . $field['key'] );
177
178 // Populate choices.
179 if ( $field['value'] ) {
180
181 // Clean value into an array of IDs.
182 $user_ids = array_map( 'intval', acf_array( $field['value'] ) );
183
184 // Find users in database (ensures all results are real).
185 $users = acf_get_users(
186 array(
187 'include' => $user_ids,
188 )
189 );
190
191 // Append.
192 if ( $users ) {
193 foreach ( $users as $user ) {
194 $field['choices'][ $user->ID ] = $this->get_result( $user, $field );
195 }
196 }
197 }
198
199 // Render.
200 acf_render_field( $field );
201 }
202
203 /**
204 * Returns the result text for a given WP_User object.
205 *
206 * @date 1/11/2013
207 * @since 5.0.0
208 *
209 * @param WP_User $user The WP_User object.
210 * @param array $field The ACF field related to this query.
211 * @param (int|string) $post_id The post_id being edited.
212 * @return string
213 */
214 function get_result( $user, $field, $post_id = 0 ) {
215
216 // Get user result item.
217 $item = acf_get_user_result( $user );
218
219 // Default $post_id to current post being edited.
220 $post_id = $post_id ? $post_id : acf_get_form_data( 'post_id' );
221
222 /**
223 * Filters the result text.
224 *
225 * @date 21/5/19
226 * @since 5.8.1
227 *
228 * @param array $args The query args.
229 * @param array $field The ACF field related to this query.
230 * @param (int|string) $post_id The post_id being edited.
231 */
232 return apply_filters( 'acf/fields/user/result', $item['text'], $user, $field, $post_id );
233 }
234
235 /**
236 * Filters the field value after it is loaded from the database.
237 *
238 * @date 23/01/13
239 * @since 3.6.0
240 *
241 * @param mixed $value The field value.
242 * @param mixed $post_id The post ID where the value is saved.
243 * @param array $field The field array containing all settings.
244 * @return mixed
245 */
246 function load_value( $value, $post_id, $field ) {
247
248 // Add compatibility for version 4.
249 if ( $value === 'null' ) {
250 return false;
251 }
252 return $value;
253 }
254
255 /**
256 * Filters the field value after it is loaded from the database but before it is returned to the front-end API.
257 *
258 * @date 23/01/13
259 * @since 3.6.0
260 *
261 * @param mixed $value The field value.
262 * @param mixed $post_id The post ID where the value is saved.
263 * @param array $field The field array containing all settings.
264 * @return mixed
265 */
266 function format_value( $value, $post_id, $field ) {
267
268 // Bail early if no value.
269 if ( ! $value ) {
270 return false;
271 }
272
273 // Clean value into an array of IDs.
274 $user_ids = array_map( 'intval', acf_array( $value ) );
275
276 // Find users in database (ensures all results are real).
277 $users = acf_get_users(
278 array(
279 'include' => $user_ids,
280 )
281 );
282
283 // Bail early if no users found.
284 if ( ! $users ) {
285 return false;
286 }
287
288 // Format values using field settings.
289 $value = array();
290 foreach ( $users as $user ) {
291
292 // Return object.
293 if ( $field['return_format'] == 'object' ) {
294 $item = $user;
295
296 // Return array.
297 } elseif ( $field['return_format'] == 'array' ) {
298 $item = array(
299 'ID' => $user->ID,
300 'user_firstname' => $user->user_firstname,
301 'user_lastname' => $user->user_lastname,
302 'nickname' => $user->nickname,
303 'user_nicename' => $user->user_nicename,
304 'display_name' => $user->display_name,
305 'user_email' => $user->user_email,
306 'user_url' => $user->user_url,
307 'user_registered' => $user->user_registered,
308 'user_description' => $user->user_description,
309 'user_avatar' => get_avatar( $user->ID ),
310 );
311
312 // Return ID.
313 } else {
314 $item = $user->ID;
315 }
316
317 // Append item
318 $value[] = $item;
319 }
320
321 // Convert to single.
322 if ( ! $field['multiple'] ) {
323 $value = array_shift( $value );
324 }
325
326 // Return.
327 return $value;
328 }
329
330 /**
331 * Filters the field value before it is saved into the database.
332 *
333 * @since 3.6.0
334 *
335 * @param mixed $value The field value.
336 * @param mixed $post_id The post ID where the value is saved.
337 * @param array $field The field array containing all settings.
338 * @return mixed $value The modified value.
339 */
340 public function update_value( $value, $post_id, $field ) {
341
342 // Bail early if no value.
343 if ( empty( $value ) ) {
344 acf_update_bidirectional_values( array(), $post_id, $field, 'user' );
345 return $value;
346 }
347
348 // Format array of values.
349 // - ensure each value is an id.
350 // - Parse each id as string for SQL LIKE queries.
351 if ( acf_is_sequential_array( $value ) ) {
352 $value = array_map( 'acf_idval', $value );
353 $value = array_map( 'strval', $value );
354
355 // Parse single value for id.
356 } else {
357 $value = acf_idval( $value );
358 }
359
360 acf_update_bidirectional_values( acf_get_array( $value ), $post_id, $field, 'user' );
361
362 // Return value.
363 return $value;
364 }
365
366 /**
367 * Callback for the AJAX query request.
368 *
369 * @date 24/10/13
370 * @since 5.0.0
371 *
372 * @param void
373 * @return void
374 */
375 function ajax_query() {
376
377 // phpcs:disable WordPress.Security.NonceVerification.Recommended
378 // Modify Request args.
379 if ( isset( $_REQUEST['s'] ) ) {
380 $_REQUEST['search'] = sanitize_text_field( $_REQUEST['s'] );
381 }
382 if ( isset( $_REQUEST['paged'] ) ) {
383 $_REQUEST['page'] = absint( $_REQUEST['paged'] );
384 }
385 // phpcs:enable WordPress.Security.NonceVerification.Recommended
386
387 // Add query hooks.
388 add_action( 'acf/ajax/query_users/init', array( $this, 'ajax_query_init' ), 10, 2 );
389 add_filter( 'acf/ajax/query_users/args', array( $this, 'ajax_query_args' ), 10, 3 );
390 add_filter( 'acf/ajax/query_users/result', array( $this, 'ajax_query_result' ), 10, 3 );
391 add_filter( 'acf/ajax/query_users/search_columns', array( $this, 'ajax_query_search_columns' ), 10, 4 );
392 // Simulate AJAX request.
393 acf_get_instance( 'ACF_Ajax_Query_Users' )->request();
394 }
395
396 /**
397 * Runs during the AJAX query initialization.
398 *
399 * @date 9/3/20
400 * @since 5.8.8
401 *
402 * @param array $request The query request.
403 * @param ACF_Ajax_Query $query The query object.
404 * @return void
405 */
406 function ajax_query_init( $request, $query ) {
407 // Require field and make sure it's a user field.
408 if ( ! $query->field || $query->field['type'] !== $this->name ) {
409 $query->send( new WP_Error( 'acf_missing_field', __( 'Error loading field.', 'acf' ), array( 'status' => 404 ) ) );
410 }
411
412 // Verify that this is a legitimate request using a separate nonce from the main AJAX nonce.
413 $nonce = acf_request_arg( 'nonce', '' );
414 $key = acf_request_arg( 'field_key', '' );
415
416 if ( ! acf_verify_ajax( $nonce, $key, true, 'user' ) ) {
417 $query->send( new WP_Error( 'acf_invalid_request', __( 'Invalid request.', 'acf' ), array( 'status' => 404 ) ) );
418 }
419 }
420
421 /**
422 * Filters the AJAX query args.
423 *
424 * @date 9/3/20
425 * @since 5.8.8
426 *
427 * @param array $args The query args.
428 * @param array $request The query request.
429 * @param ACF_Ajax_Query $query The query object.
430 * @return array
431 */
432 function ajax_query_args( $args, $request, $query ) {
433
434 // Add specific roles.
435 if ( $query->field['role'] ) {
436 $args['role__in'] = acf_array( $query->field['role'] );
437 }
438
439 /**
440 * Filters the query args.
441 *
442 * @date 21/5/19
443 * @since 5.8.1
444 *
445 * @param array $args The query args.
446 * @param array $field The ACF field related to this query.
447 * @param (int|string) $post_id The post_id being edited.
448 */
449 return apply_filters( 'acf/fields/user/query', $args, $query->field, $query->post_id );
450 }
451
452 /**
453 * Filters the WP_User_Query search columns.
454 *
455 * @date 9/3/20
456 * @since 5.8.8
457 *
458 * @param array $columns An array of column names to be searched.
459 * @param string $search The search term.
460 * @param WP_User_Query $WP_User_Query The WP_User_Query instance.
461 * @return array
462 */
463 function ajax_query_search_columns( $columns, $search, $WP_User_Query, $query ) {
464
465 /**
466 * Filters the column names to be searched.
467 *
468 * @date 21/5/19
469 * @since 5.8.1
470 *
471 * @param array $columns An array of column names to be searched.
472 * @param string $search The search term.
473 * @param WP_User_Query $WP_User_Query The WP_User_Query instance.
474 * @param array $field The ACF field related to this query.
475 */
476 return apply_filters( 'acf/fields/user/search_columns', $columns, $search, $WP_User_Query, $query->field );
477 }
478
479 /**
480 * Filters the AJAX Query result.
481 *
482 * @date 9/3/20
483 * @since 5.8.8
484 *
485 * @param array $item The choice id and text.
486 * @param WP_User $user The user object.
487 * @param ACF_Ajax_Query $query The query object.
488 * @return array
489 */
490 function ajax_query_result( $item, $user, $query ) {
491
492 /**
493 * Filters the result text.
494 *
495 * @date 21/5/19
496 * @since 5.8.1
497 *
498 * @param string The result text.
499 * @param WP_User $user The user object.
500 * @param array $field The ACF field related to this query.
501 * @param (int|string) $post_id The post_id being edited.
502 */
503 $item['text'] = apply_filters( 'acf/fields/user/result', $item['text'], $user, $query->field, $query->post_id );
504 return $item;
505 }
506
507 /**
508 * Return an array of data formatted for use in a select2 AJAX response.
509 *
510 * @date 15/10/2014
511 * @since 5.0.9
512 * @deprecated 5.8.9
513 *
514 * @param array $args An array of query args.
515 * @return array
516 */
517 function get_ajax_query( $options = array() ) {
518 _deprecated_function( __FUNCTION__, '5.8.9' );
519 return array();
520 }
521
522 /**
523 * Filters the WP_User_Query search columns.
524 *
525 * @date 15/10/2014
526 * @since 5.0.9
527 * @deprecated 5.8.9
528 *
529 * @param array $columns An array of column names to be searched.
530 * @param string $search The search term.
531 * @param WP_User_Query $WP_User_Query The WP_User_Query instance.
532 * @return array
533 */
534 function user_search_columns( $columns, $search, $WP_User_Query ) {
535 _deprecated_function( __FUNCTION__, '5.8.9' );
536 return $columns;
537 }
538
539 /**
540 * Validates user fields updated via the REST API.
541 *
542 * @param boolean $valid The current validity booleean
543 * @param integer $value The value of the field
544 * @param array $field The field array
545 * @return boolean|WP_Error
546 */
547 public function validate_rest_value( $valid, $value, $field ) {
548 if ( is_null( $value ) ) {
549 return $valid;
550 }
551
552 $param = sprintf( '%s[%s]', $field['prefix'], $field['name'] );
553 $data = array( 'param' => $param );
554 $value = is_array( $value ) ? $value : array( $value );
555
556 $invalid_users = array();
557 $insufficient_roles = array();
558
559 foreach ( $value as $user_id ) {
560 $user_data = get_userdata( $user_id );
561 if ( ! $user_data ) {
562 $invalid_users[] = $user_id;
563 continue;
564 }
565
566 if ( empty( $field['role'] ) ) {
567 continue;
568 }
569
570 $has_roles = count( array_intersect( $field['role'], $user_data->roles ) );
571 if ( ! $has_roles ) {
572 $insufficient_roles[] = $user_id;
573 }
574 }
575
576 if ( count( $invalid_users ) ) {
577 $error = sprintf(
578 __( '%1$s must have a valid user ID.', 'acf' ),
579 $param
580 );
581 $data['value'] = $invalid_users;
582 return new WP_Error( 'rest_invalid_param', $error, $data );
583 }
584
585 if ( count( $insufficient_roles ) ) {
586 $error = sprintf(
587 _n(
588 '%1$s must have a user with the %2$s role.',
589 '%1$s must have a user with one of the following roles: %2$s',
590 count( $field['role'] ),
591 'acf'
592 ),
593 $param,
594 count( $field['role'] ) > 1 ? implode( ', ', $field['role'] ) : $field['role'][0]
595 );
596 $data['value'] = $insufficient_roles;
597 return new WP_Error( 'rest_invalid_param', $error, $data );
598 }
599
600 return $valid;
601 }
602
603 /**
604 * Return the schema array for the REST API.
605 *
606 * @param array $field
607 * @return array
608 */
609 public function get_rest_schema( array $field ) {
610 $schema = array(
611 'type' => array( 'integer', 'array', 'null' ),
612 'required' => ! empty( $field['required'] ),
613 'items' => array(
614 'type' => 'integer',
615 ),
616 );
617
618 if ( empty( $field['allow_null'] ) ) {
619 $schema['minItems'] = 1;
620 }
621
622 if ( empty( $field['multiple'] ) ) {
623 $schema['maxItems'] = 1;
624 }
625
626 return $schema;
627 }
628
629 /**
630 * @see \acf_field::get_rest_links()
631 * @param mixed $value The raw (unformatted) field value.
632 * @param integer|string $post_id
633 * @param array $field
634 * @return array
635 */
636 public function get_rest_links( $value, $post_id, array $field ) {
637 $links = array();
638
639 if ( empty( $value ) ) {
640 return $links;
641 }
642
643 foreach ( (array) $value as $object_id ) {
644 $links[] = array(
645 'rel' => 'acf:user',
646 'href' => rest_url( '/wp/v2/users/' . $object_id ),
647 'embeddable' => true,
648 );
649 }
650
651 return $links;
652 }
653
654 /**
655 * Apply basic formatting to prepare the value for default REST output.
656 *
657 * @param mixed $value
658 * @param string|integer $post_id
659 * @param array $field
660 * @return mixed
661 */
662 public function format_value_for_rest( $value, $post_id, array $field ) {
663 return acf_format_numerics( $value );
664 }
665
666 /**
667 * Formats the field value for JSON-LD output.
668 *
669 * @since 6.8.0
670 *
671 * @param mixed $value The value of the field.
672 * @param integer|string $post_id The ID of the post.
673 * @param array $field The field array.
674 * @return mixed
675 */
676 public function format_value_for_jsonld( $value, $post_id, $field ) {
677 if ( empty( $value ) ) {
678 return null;
679 }
680
681 // Get output format with fallback.
682 $output_format = $field['schema_output_format'] ?? '';
683 if ( empty( $output_format ) ) {
684 $property = $field['schema_property'] ?? '';
685 $output_format = \ACF\AI\GEO\Schema::get_default_output_format( $this->name, $property );
686 }
687
688 // Default to Person if still empty.
689 if ( empty( $output_format ) ) {
690 $output_format = 'Person';
691 }
692
693 $field['return_format'] = 'object';
694 $users = $this->format_value( $value, $post_id, $field );
695
696 if ( ! $users ) {
697 return null;
698 }
699
700 // Handle single user.
701 if ( $users instanceof \WP_User ) {
702 return $this->format_user_for_jsonld( $users, $output_format );
703 }
704
705 // Handle multiple users.
706 $formatted = array();
707 if ( is_array( $users ) ) {
708 foreach ( $users as $user ) {
709 if ( $user instanceof \WP_User ) {
710 $formatted[] = $this->format_user_for_jsonld( $user, $output_format );
711 }
712 }
713 }
714
715 return $formatted;
716 }
717
718 /**
719 * Format a single user for JSON-LD output.
720 *
721 * @since 6.8.0
722 *
723 * @param \WP_User $user The user object.
724 * @param string $output_format The output format (Person or Organization).
725 * @return array The formatted user data.
726 */
727 private function format_user_for_jsonld( $user, $output_format ) {
728 $data = array(
729 '@type' => $output_format,
730 'name' => $user->get( 'display_name' ),
731 );
732
733 // Add URL if available.
734 $url = $user->get( 'user_url' );
735 if ( $url ) {
736 $data['url'] = $url;
737 }
738
739 return $data;
740 }
741
742 /**
743 * Returns an array of JSON-LD Property output types that are supported by this field type.
744 *
745 * @since 6.8
746 *
747 * @return string[]
748 */
749 public function get_jsonld_output_types(): array {
750 return array( 'Person', 'Organization' );
751 }
752 }
753
754
755 // initialize
756 acf_register_field_type( 'ACF_Field_User' );
757 endif; // class_exists check
758