PluginProbe ʕ •ᴥ•ʔ
Advanced Custom Fields (ACF®) / 6.8.8
Advanced Custom Fields (ACF®) v6.8.8
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 / forms / form-user.php
advanced-custom-fields / includes / forms Last commit date
form-attachment.php 5 months ago form-comment.php 4 weeks ago form-customizer.php 5 months ago form-front.php 2 months ago form-gutenberg.php 5 months ago form-nav-menu.php 5 months ago form-post.php 3 months ago form-taxonomy.php 2 months ago form-user.php 4 weeks ago form-widget.php 5 months ago index.php 2 years ago
form-user.php
453 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 ( ! defined( 'ABSPATH' ) ) {
13 exit; // Exit if accessed directly
14 }
15
16 if ( ! class_exists( 'ACF_Form_User' ) ) :
17
18 class ACF_Form_User {
19
20 /** @var string The current view (new, edit, register) */
21 var $view = '';
22
23
24 /**
25 * This function will setup the class functionality
26 *
27 * @type function
28 * @date 5/03/2014
29 * @since 5.0.0
30 *
31 * @param n/a
32 * @return n/a
33 */
34 function __construct() {
35
36 // enqueue
37 add_action( 'admin_enqueue_scripts', array( $this, 'admin_enqueue_scripts' ) );
38 add_action( 'login_form_register', array( $this, 'login_form_register' ) );
39
40 // render
41 add_action( 'show_user_profile', array( $this, 'render_edit' ) );
42 add_action( 'edit_user_profile', array( $this, 'render_edit' ) );
43 add_action( 'user_new_form', array( $this, 'render_new' ) );
44 add_action( 'register_form', array( $this, 'render_register' ) );
45
46 // save
47 add_action( 'user_register', array( $this, 'save_user' ) );
48 add_action( 'profile_update', array( $this, 'save_user' ) );
49
50 // Perform validation before new user is registered.
51 add_filter( 'registration_errors', array( $this, 'filter_registration_errors' ), 10, 3 );
52 }
53
54
55 /**
56 * admin_enqueue_scripts
57 *
58 * Checks current screen and enqueues scripts
59 *
60 * @date 17/4/18
61 * @since 5.6.9
62 *
63 * @param void
64 * @return void
65 */
66 function admin_enqueue_scripts() {
67
68 // bail early if not valid screen
69 if ( ! acf_is_screen( array( 'profile', 'user', 'user-edit', 'profile-network', 'user-network', 'user-edit-network' ) ) ) {
70 return;
71 }
72
73 // enqueue
74 acf_enqueue_scripts();
75 }
76
77
78 /**
79 * login_form_register
80 *
81 * Customizes and enqueues scripts
82 *
83 * @date 17/4/18
84 * @since 5.6.9
85 *
86 * @param void
87 * @return void
88 */
89 function login_form_register() {
90
91 // customize action prefix so that "admin_head" = "login_head"
92 acf_enqueue_scripts(
93 array(
94 'context' => 'login',
95 )
96 );
97 }
98
99
100 /**
101 * Called during the user register form
102 *
103 * @type function
104 * @date 8/10/13
105 * @since 5.0.0
106 *
107 * @param void
108 * @return void
109 */
110 function render_register() {
111
112 // render
113 $this->render(
114 array(
115 'user_id' => 0,
116 'view' => 'register',
117 'el' => 'div',
118 )
119 );
120 }
121
122
123 /**
124 * Called during the user edit form
125 *
126 * @type function
127 * @date 8/10/13
128 * @since 5.0.0
129 *
130 * @param void
131 * @return void
132 */
133 function render_edit( $user ) {
134
135 // add compatibility with front-end user profile edit forms such as bbPress
136 if ( ! is_admin() ) {
137 acf_enqueue_scripts();
138 }
139
140 // render
141 $this->render(
142 array(
143 'user_id' => $user->ID,
144 'view' => 'edit',
145 'el' => 'tr',
146 )
147 );
148 }
149
150
151 /**
152 * description
153 *
154 * @type function
155 * @date 8/10/13
156 * @since 5.0.0
157 *
158 * @param $post_id (int)
159 * @return $post_id (int)
160 */
161 function render_new() {
162
163 // Multisite uses a different 'user-new.php' form. Don't render fields here
164 if ( is_multisite() ) {
165 return;
166 }
167
168 // render
169 $this->render(
170 array(
171 'user_id' => 0,
172 'view' => 'add',
173 'el' => 'tr',
174 )
175 );
176 }
177
178
179 /**
180 * This function will render ACF fields for a given $post_id parameter
181 *
182 * @type function
183 * @since 5.0.0
184 *
185 * @param $user_id (int) this can be set to 0 for a new user
186 * @param $user_form (string) used for location rule matching. edit | add | register
187 * @param $el (string)
188 * @return n/a
189 */
190 function render( $args = array() ) {
191
192 // Allow $_POST data to persist across form submission attempts.
193 if ( isset( $_POST['acf'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing
194 add_filter( 'acf/pre_load_value', array( $this, 'filter_pre_load_value' ), 10, 3 );
195 }
196
197 // defaults
198 $args = wp_parse_args(
199 $args,
200 array(
201 'user_id' => 0,
202 'view' => 'edit',
203 'el' => 'tr',
204 )
205 );
206
207 // vars
208 $post_id = 'user_' . $args['user_id'];
209
210 // get field groups
211 $field_groups = acf_get_field_groups(
212 array(
213 'user_id' => $args['user_id'] ? $args['user_id'] : 'new',
214 'user_form' => $args['view'],
215 )
216 );
217
218 // bail early if no field groups
219 if ( empty( $field_groups ) ) {
220 return;
221 }
222
223 // form data
224 acf_form_data(
225 array(
226 'screen' => 'user',
227 'post_id' => $post_id,
228 'validation' => ( $args['view'] == 'register' ) ? 0 : 1,
229 )
230 );
231
232 // elements
233 $before = '<table class="form-table"><tbody>';
234 $after = '</tbody></table>';
235
236 if ( $args['el'] == 'div' ) {
237 $before = '<div class="acf-user-' . esc_attr( $args['view'] ) . '-fields acf-fields -clear">';
238 $after = '</div>';
239 }
240
241 // loop
242 foreach ( $field_groups as $field_group ) {
243
244 // vars
245 $fields = acf_get_fields( $field_group );
246
247 // title
248 if ( $field_group['style'] === 'default' ) {
249 echo '<h2>' . acf_esc_html( acf_get_field_group_title( $field_group ) ) . '</h2>';
250 }
251
252 // render
253 echo $before; //phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- safe HTML string.
254 acf_render_fields( $fields, $post_id, $args['el'], $field_group['instruction_placement'] );
255 echo $after; //phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- safe HTML string.
256 }
257
258 // actions
259 add_action( 'acf/input/admin_footer', array( $this, 'admin_footer' ), 10, 1 );
260 }
261
262
263 /**
264 * description
265 *
266 * @type function
267 * @date 27/03/2015
268 * @since 5.1.5
269 *
270 * @param $post_id (int)
271 * @return $post_id (int)
272 */
273 function admin_footer() {
274
275 // script
276 ?>
277 <script type="text/javascript">
278 (function($) {
279
280 // vars
281 var view = '<?php echo esc_attr( $this->view ); ?>';
282
283 // add missing spinners
284 var $submit = $('input.button-primary');
285 if( !$submit.next('.spinner').length ) {
286 $submit.after('<span class="spinner"></span>');
287 }
288
289 })(jQuery);
290 </script>
291 <?php
292 }
293
294 /**
295 * Saves ACF field values to a user.
296 *
297 * @since 5.0.0
298 *
299 * @param integer $user_id The ID of the user being saved.
300 * @return integer|void
301 */
302 public function save_user( $user_id ) {
303 if ( ! acf_verify_nonce( 'user' ) ) {
304 return $user_id;
305 }
306
307 if ( isset( $_POST['acf'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Verified above.
308 if ( ! is_array( $_POST['acf'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Verified above.
309 return $user_id;
310 }
311
312 // Restrict $_POST['acf'] to keys of fields belonging to field groups that apply to this user.
313 $allowed_keys = $this->get_allowed_field_keys( $user_id );
314 $_POST['acf'] = array_intersect_key( $_POST['acf'], array_flip( $allowed_keys ) ); // phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.ValidatedSanitizedInput.MissingUnslash -- Verified above; sanitized downstream.
315
316 if ( empty( $_POST['acf'] ) ) {
317 return $user_id;
318 }
319 }
320
321 // save
322 if ( acf_validate_save_post( true ) ) {
323 acf_save_post( "user_$user_id" );
324 }
325 }
326
327 /**
328 * Returns the top-level ACF field keys that are allowed to be saved for
329 * the given user in the current save context. On user_register the new
330 * user has just been created via either the public register form or the
331 * admin add-user form; on profile_update the user was rendered via the
332 * edit form (self-edit or admin-edit-other).
333 *
334 * @since 6.8.7
335 *
336 * @param integer $user_id The user being saved.
337 * @return array
338 */
339 private function get_allowed_field_keys( $user_id ) {
340 if ( current_action() === 'user_register' ) {
341 $queries = array(
342 array(
343 'user_id' => 'new',
344 'user_form' => 'register',
345 ),
346 array(
347 'user_id' => 'new',
348 'user_form' => 'add',
349 ),
350 );
351 } else {
352 $queries = array(
353 array(
354 'user_id' => $user_id,
355 'user_form' => 'edit',
356 ),
357 );
358 }
359
360 $keys = array();
361 foreach ( $queries as $query ) {
362 foreach ( acf_get_field_groups( $query ) as $field_group ) {
363 foreach ( acf_get_fields( $field_group ) as $field ) {
364 $prefix = $field['prefix'] ?? 'acf';
365
366 if ( $prefix === 'acf' ) {
367 if ( ! empty( $field['key'] ) ) {
368 $keys[] = $field['key'];
369 }
370 } elseif ( preg_match( '/^acf\[([^]]+)]$/', $prefix, $matches ) ) {
371 $keys[] = $matches[1];
372 }
373 }
374 }
375 }
376
377 $keys = array_values( array_unique( array_filter( $keys ) ) );
378
379 /**
380 * Filters the list of $_POST['acf'] keys a user save is allowed to persist.
381 *
382 * Use this to permit additional field keys when a developer dynamically
383 * injects fields into the register/add/edit user form via JavaScript
384 * that aren't part of a field group whose location rules target this user.
385 *
386 * @since 6.8.7
387 *
388 * @param array $keys The allowed top-level $_POST['acf'] keys.
389 * @param int $user_id The user being saved.
390 */
391 $keys = apply_filters( 'acf/form/user/allowed_field_keys', $keys, $user_id );
392
393 // Re-normalize after the filter so a misbehaving callback can't break array_flip().
394 $keys = array_filter( (array) $keys, 'is_scalar' );
395 return array_values( array_unique( array_filter( array_map( 'strval', $keys ) ) ) );
396 }
397
398 /**
399 * filter_registration_errors
400 *
401 * Validates $_POST data and appends any errors to prevent new user registration.
402 *
403 * @date 12/7/19
404 * @since 5.8.1
405 *
406 * @param WP_Error $errors A WP_Error object containing any errors encountered during registration.
407 * @param string $sanitized_user_login User's username after it has been sanitized.
408 * @param string $user_email User's email.
409 * @return WP_Error
410 */
411 function filter_registration_errors( $errors, $sanitized_user_login, $user_email ) {
412 if ( ! acf_validate_save_post() ) {
413 $acf_errors = acf_get_validation_errors();
414 foreach ( $acf_errors as $acf_error ) {
415 $errors->add(
416 acf_idify( $acf_error['input'] ),
417 acf_esc_html( acf_punctify( sprintf( __( '<strong>Error</strong>: %s', 'acf' ), $acf_error['message'] ) ) )
418 );
419 }
420 }
421 return $errors;
422 }
423
424 /**
425 * filter_pre_load_value
426 *
427 * Checks if a $_POST value exists for this field to allow persistent values.
428 *
429 * @date 12/7/19
430 * @since 5.8.2
431 *
432 * @param null $null A null placeholder.
433 * @param (int|string) $post_id The post id.
434 * @param array $field The field array.
435 * @return mixed
436 */
437 function filter_pre_load_value( $null, $post_id, $field ) {
438 $field_key = $field['key'];
439 // phpcs:disable WordPress.Security.NonceVerification.Missing -- Verified in save_user().
440 if ( isset( $_POST['acf'][ $field_key ] ) ) {
441 return $_POST['acf'][ $field_key ]; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Sanitized elsewhere.
442 }
443 // phpcs:enable WordPress.Security.NonceVerification.Missing
444 return $null;
445 }
446 }
447
448 // instantiate
449 acf_new_instance( 'ACF_Form_User' );
450 endif; // class_exists check
451
452 ?>
453