PluginProbe
Advanced Custom Fields (ACF®) / 6.1.0
Advanced Custom Fields (ACF®) v6.1.0
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 All 230 releases
advanced-custom-fields / includes / admin / post-types / admin-field-group.php
admin-field-group.php
600 lines 16.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * ACF Admin Field Group Class
4 *
5 * @class acf_admin_field_group
6 *
7 * @package ACF
8 * @subpackage Admin
9 */
10
11 if ( ! class_exists( 'acf_admin_field_group' ) ) :
12
13 /**
14 * ACF Admin Field Group Class
15 *
16 * All the logic for editing a field group
17 */
18 class acf_admin_field_group extends ACF_Admin_Internal_Post_Type {
19
20 /**
21 * The slug for the internal post type.
22 *
23 * @since 6.1
24 * @var string
25 */
26 public $post_type = 'acf-field-group';
27
28 /**
29 * The admin body class used for the post type.
30 *
31 * @since 6.1
32 * @var string
33 */
34 public $admin_body_class = 'acf-admin-single-field-group';
35
36 /**
37 * Constructs the class.
38 *
39 * @since 5.0.0
40 *
41 * @return void
42 */
43 public function __construct() {
44 parent::__construct();
45
46 add_action( 'wp_ajax_acf/field_group/render_field_settings', array( $this, 'ajax_render_field_settings' ) );
47 add_action( 'wp_ajax_acf/field_group/render_location_rule', array( $this, 'ajax_render_location_rule' ) );
48 add_action( 'wp_ajax_acf/field_group/move_field', array( $this, 'ajax_move_field' ) );
49 }
50
51 /**
52 * This function will customize the message shown when editing a field group
53 *
54 * @since 5.0.0
55 *
56 * @param array $messages Post type messages.
57 * @return array
58 */
59 public function post_updated_messages( $messages ) {
60 $messages['acf-field-group'] = array(
61 0 => '', // Unused. Messages start at index 1.
62 1 => __( 'Field group updated.', 'acf' ),
63 2 => __( 'Field group updated.', 'acf' ),
64 3 => __( 'Field group deleted.', 'acf' ),
65 4 => __( 'Field group updated.', 'acf' ),
66 5 => false, // field group does not support revisions.
67 6 => __( 'Field group published.', 'acf' ),
68 7 => __( 'Field group saved.', 'acf' ),
69 8 => __( 'Field group submitted.', 'acf' ),
70 9 => __( 'Field group scheduled for.', 'acf' ),
71 10 => __( 'Field group draft updated.', 'acf' ),
72 );
73
74 return $messages;
75 }
76
77 /**
78 * Enqueues any scripts necessary for internal post type.
79 *
80 * @since 5.0.0
81 *
82 * @return void
83 */
84 public function admin_enqueue_scripts() {
85 parent::admin_enqueue_scripts();
86
87 acf_localize_text(
88 array(
89 'The string "field_" may not be used at the start of a field name' => __( 'The string "field_" may not be used at the start of a field name', 'acf' ),
90 'This field cannot be moved until its changes have been saved' => __( 'This field cannot be moved until its changes have been saved', 'acf' ),
91 'Field group title is required' => __( 'Field group title is required', 'acf' ),
92 'Move field group to trash?' => __( 'Move field group to trash?', 'acf' ),
93 'No toggle fields available' => __( 'No toggle fields available', 'acf' ),
94 'Move Custom Field' => __( 'Move Custom Field', 'acf' ),
95 'Close modal' => __( 'Close modal', 'acf' ),
96 'Field moved to other group' => __( 'Field moved to other group', 'acf' ),
97 'Field groups linked successfully.' => __( 'Field groups linked successfully.', 'acf' ),
98 'Checked' => __( 'Checked', 'acf' ),
99 '(no label)' => __( '(no label)', 'acf' ),
100 '(this field)' => __( '(this field)', 'acf' ),
101 'copy' => __( 'copy', 'acf' ),
102 'or' => __( 'or', 'acf' ),
103 'Show this field group if' => __( 'Show this field group if', 'acf' ),
104 'Null' => __( 'Null', 'acf' ),
105 'PRO Only' => __( 'PRO Only', 'acf' ),
106
107 // Conditions.
108 'Has any value' => __( 'Has any value', 'acf' ),
109 'Has no value' => __( 'Has no value', 'acf' ),
110 'Value is equal to' => __( 'Value is equal to', 'acf' ),
111 'Value is not equal to' => __( 'Value is not equal to', 'acf' ),
112 'Value matches pattern' => __( 'Value matches pattern', 'acf' ),
113 'Value contains' => __( 'Value contains', 'acf' ),
114 'Value is greater than' => __( 'Value is greater than', 'acf' ),
115 'Value is less than' => __( 'Value is less than', 'acf' ),
116 'Selection is greater than' => __( 'Selection is greater than', 'acf' ),
117 'Selection is less than' => __( 'Selection is less than', 'acf' ),
118
119 // Field type selection modal.
120 'Type to search...' => __( 'Type to search...', 'acf' ),
121 )
122 );
123
124 acf_localize_data(
125 array(
126 'fieldTypes' => acf_get_field_types_info(),
127 'fieldCategoriesL10n' => acf_get_field_categories_i18n(),
128 'PROUpgradeURL' => acf_add_url_utm_tags( 'https://www.advancedcustomfields.com/pro/', 'ACF upgrade', 'field-type-selection' ),
129 'PROFieldTypes' => acf_get_pro_field_types(),
130 )
131 );
132
133 do_action( 'acf/field_group/admin_enqueue_scripts' );
134 }
135
136 /**
137 * Set up functionality for the field group edit page.
138 *
139 * @since 3.1.8
140 *
141 * @return void
142 */
143 public function admin_head() {
144 global $post, $field_group;
145
146 // Set global var.
147 $field_group = acf_get_field_group( $post->ID );
148
149 // metaboxes.
150 add_meta_box( 'acf-field-group-fields', __( 'Fields', 'acf' ), array( $this, 'mb_fields' ), 'acf-field-group', 'normal', 'high' );
151 if ( ! defined( 'ACF_PRO' ) || ! ACF_PRO ) {
152 add_meta_box( 'acf-field-group-pro-features', 'ACF PRO', array( $this, 'mb_pro_features' ), 'acf-field-group', 'normal', 'high' );
153 }
154 add_meta_box( 'acf-field-group-options', __( 'Settings', 'acf' ), array( $this, 'mb_options' ), 'acf-field-group', 'normal', 'high' );
155
156 // actions.
157 add_action( 'post_submitbox_misc_actions', array( $this, 'post_submitbox_misc_actions' ), 10, 0 );
158 add_action( 'edit_form_after_title', array( $this, 'edit_form_after_title' ), 10, 0 );
159
160 // filters.
161 add_filter( 'screen_settings', array( $this, 'screen_settings' ), 10, 1 );
162 add_filter( 'get_user_option_screen_layout_acf-field-group', array( $this, 'screen_layout' ), 10, 1 );
163
164 // 3rd party hook.
165 do_action( 'acf/field_group/admin_head' );
166 }
167
168 /**
169 * This action will allow ACF to render metaboxes after the title.
170 *
171 * @return void
172 */
173 public function edit_form_after_title() {
174 global $post;
175
176 // Render post data.
177 acf_form_data(
178 array(
179 'screen' => 'field_group',
180 'post_id' => $post->ID,
181 'delete_fields' => 0,
182 'validation' => 0,
183 )
184 );
185 }
186
187 /**
188 * This function will add extra HTML to the acf form data element
189 *
190 * @since 5.3.8
191 *
192 * @param array $args Arguments array to pass through to action.
193 * @return void
194 */
195 public function form_data( $args ) {
196 do_action( 'acf/field_group/form_data', $args );
197 }
198
199 /**
200 * This function will append extra l10n strings to the acf JS object
201 *
202 * @since 5.3.8
203 *
204 * @param array $l10n The array of translated strings.
205 * @return array $l10n
206 */
207 public function admin_l10n( $l10n ) {
208 return apply_filters( 'acf/field_group/admin_l10n', $l10n );
209 }
210
211 /**
212 * Admin footer third party hook support
213 *
214 * @since 5.3.2
215 *
216 * @return void
217 */
218 public function admin_footer() {
219 do_action( 'acf/field_group/admin_footer' );
220 }
221
222 /**
223 * Screen settings html output
224 *
225 * @since 3.6.0
226 *
227 * @param string $html Current screen settings HTML.
228 * @return string $html
229 */
230 public function screen_settings( $html ) {
231 $show_field_keys = acf_get_user_setting( 'show_field_keys' ) ? 'checked="checked"' : '';
232 $show_field_settings_tabs = acf_get_user_setting( 'show_field_settings_tabs', true ) ? 'checked="checked"' : '';
233 $hide_field_settings_tabs = apply_filters( 'acf/field_group/disable_field_settings_tabs', false );
234
235 $html .= '<div id="acf-append-show-on-screen" class="acf-hidden">';
236 $html .= '<label for="acf-field-key-hide"><input id="acf-field-key-hide" type="checkbox" value="1" name="show_field_keys" ' . $show_field_keys . ' /> ' . __( 'Field Keys', 'acf' ) . '</label>';
237
238 if ( ! $hide_field_settings_tabs ) {
239 $html .= '<label for="acf-field-settings-tabs"><input id="acf-field-settings-tabs" type="checkbox" value="1" name="show_field_settings_tabs" ' . $show_field_settings_tabs . ' />' . __( 'Field Settings Tabs', 'acf' ) . '</label>';
240 }
241
242 $html .= '</div>';
243
244 return $html;
245 }
246
247 /**
248 * Sets the "Edit Field Group" screen to use a one-column layout.
249 *
250 * @param int $columns Number of columns for layout.
251 *
252 * @return int
253 */
254 public function screen_layout( $columns = 0 ) {
255 return 1;
256 }
257
258 /**
259 * This function will customize the publish metabox
260 *
261 * @since 5.2.9
262 *
263 * @return void
264 */
265 public function post_submitbox_misc_actions() {
266 global $field_group;
267 $status_label = $field_group['active'] ? _x( 'Active', 'post status', 'acf' ) : _x( 'Inactive', 'post status', 'acf' );
268
269 ?>
270 <script type="text/javascript">
271 (function($) {
272 $('#post-status-display').html( '<?php echo esc_html( $status_label ); ?>' );
273 })(jQuery);
274 </script>
275 <?php
276 }
277
278 /**
279 * Saves field group data.
280 *
281 * @since 1.0.0
282 *
283 * @param int $post_id The post ID.
284 * @param WP_Post $post The post object.
285 *
286 * @return int $post_id
287 */
288 public function save_post( $post_id, $post ) {
289 if ( ! $this->verify_save_post( $post_id, $post ) ) {
290 return $post_id;
291 }
292
293 // disable filters to ensure ACF loads raw data from DB.
294 acf_disable_filters();
295
296 // save fields.
297 // phpcs:disable WordPress.Security.NonceVerification.Missing -- Validated by WordPress.
298 if ( ! empty( $_POST['acf_fields'] ) ) {
299
300 // loop.
301 foreach ( $_POST['acf_fields'] as $field ) { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Sanitized when saved.
302
303 if ( ! isset( $field['key'] ) ) {
304 continue;
305 }
306
307 // vars.
308 $specific = false;
309 $save = acf_extract_var( $field, 'save' );
310
311 // only saved field if has changed.
312 if ( $save == 'meta' ) {
313 $specific = array(
314 'menu_order',
315 'post_parent',
316 );
317 }
318
319 // set parent.
320 if ( ! $field['parent'] ) {
321 $field['parent'] = $post_id;
322 }
323
324 // save field.
325 acf_update_field( $field, $specific );
326 }
327 }
328
329 // delete fields.
330 if ( $_POST['_acf_delete_fields'] ) { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Sanitized below.
331
332 // clean.
333 $ids = explode( '|', $_POST['_acf_delete_fields'] ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Sanitized below.
334 $ids = array_map( 'intval', $ids );
335
336 // loop.
337 foreach ( $ids as $id ) {
338
339 // bai early if no id.
340 if ( ! $id ) {
341 continue;
342 }
343
344 // delete.
345 acf_delete_field( $id );
346 }
347 }
348
349 $_POST['acf_field_group']['ID'] = $post_id;
350 // phpcs:disable WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Sanitized when saved.
351 $_POST['acf_field_group']['title'] = $_POST['post_title'];
352
353 // save field group.
354 acf_update_field_group( $_POST['acf_field_group'] );
355 // phpcs:enable WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
356 // phpcs:enable WordPress.Security.NonceVerification.Missing
357
358 return $post_id;
359 }
360
361 /**
362 * This function will render the HTML for the metabox 'acf-field-group-fields'
363 *
364 * @since 5.0.0
365 *
366 * @return void
367 */
368 public function mb_fields() {
369 global $field_group;
370
371 $view = array(
372 'fields' => acf_get_fields( $field_group ),
373 'parent' => 0,
374 );
375
376 acf_get_view( $this->post_type . '/fields', $view );
377 }
378
379 /**
380 * This function will render the HTML for the metabox 'acf-field-group-pro-features'
381 *
382 * @since 6.0.0
383 *
384 * @return void
385 */
386 public function mb_pro_features() {
387 acf_get_view( $this->post_type . '/pro-features' );
388 }
389
390 /**
391 * This function will render the HTML for the metabox 'acf-field-group-options'
392 *
393 * @since 5.0.0
394 *
395 * @return void
396 */
397 public function mb_options() {
398 global $field_group;
399
400 // Field group key (leave in for compatibility).
401 if ( ! acf_is_field_group_key( $field_group['key'] ) ) {
402 $field_group['key'] = uniqid( 'group_' );
403 }
404
405 acf_get_view( $this->post_type . '/options' );
406 }
407
408 /**
409 * This function can be accessed via an AJAX action and will return the result from the render_location_value function
410 *
411 * @since 5.0.0
412 *
413 * @return void
414 */
415 public function ajax_render_location_rule() {
416 // validate.
417 if ( ! acf_verify_ajax() ) {
418 die();
419 }
420
421 // verify user capability.
422 if ( ! acf_current_user_can_admin() ) {
423 die();
424 }
425
426 // validate rule.
427 $rule = acf_validate_location_rule( acf_sanitize_request_args( $_POST['rule'] ) );
428
429 acf_get_view(
430 'acf-field-group/location-rule',
431 array(
432 'rule' => $rule,
433 )
434 );
435
436 die();
437 }
438
439 /**
440 * This function will return HTML containing the field's settings based on it's new type
441 *
442 * @since 5.0.0
443 *
444 * @return void
445 */
446 public function ajax_render_field_settings() {
447 // Verify the current request.
448 if ( ! acf_verify_ajax() || ! acf_current_user_can_admin() ) {
449 wp_send_json_error();
450 }
451
452 // Make sure we have a field.
453 $field = acf_maybe_get_POST( 'field' );
454 if ( ! $field ) {
455 wp_send_json_error();
456 }
457
458 $field['prefix'] = acf_maybe_get_POST( 'prefix' );
459 $field = acf_get_valid_field( $field );
460 $tabs = acf_get_combined_field_type_settings_tabs();
461 $tab_keys = array_keys( $tabs );
462 $sections = array();
463
464 foreach ( $tab_keys as $tab ) {
465 ob_start();
466
467 if ( 'general' === $tab ) {
468 // Back-compat for fields not using tab-specific hooks.
469 do_action( "acf/render_field_settings/type={$field['type']}", $field );
470 }
471
472 do_action( "acf/field_group/render_field_settings_tab/{$tab}/type={$field['type']}", $field );
473 do_action( "acf/render_field_{$tab}_settings/type={$field['type']}", $field );
474
475 $sections[ $tab ] = ob_get_clean();
476 }
477
478 wp_send_json_success( $sections );
479 }
480
481 /**
482 * Move field AJAX function
483 *
484 * @since 5.0.0
485 *
486 * @return void No return, HTML output for AJAX consumption.
487 */
488 public function ajax_move_field() {
489 // disable filters to ensure ACF loads raw data from DB.
490 acf_disable_filters();
491
492 // phpcs:disable WordPress.Security.NonceVerification.Missing
493 $args = acf_parse_args(
494 $_POST,
495 array(
496 'nonce' => '',
497 'post_id' => 0,
498 'field_id' => 0,
499 'field_group_id' => 0,
500 )
501 );
502 // phpcs:enable WordPress.Security.NonceVerification.Missing
503
504 // verify nonce.
505 if ( ! wp_verify_nonce( $args['nonce'], 'acf_nonce' ) ) {
506 die();
507 }
508
509 // verify user capability.
510 if ( ! acf_current_user_can_admin() ) {
511 die();
512 }
513
514 // confirm?
515 if ( $args['field_id'] && $args['field_group_id'] ) {
516
517 // vars.
518 $field = acf_get_field( $args['field_id'] );
519 $field_group = acf_get_field_group( $args['field_group_id'] );
520
521 // update parent.
522 $field['parent'] = $field_group['ID'];
523
524 // remove conditional logic.
525 $field['conditional_logic'] = 0;
526
527 // update field.
528 acf_update_field( $field );
529
530 // Output HTML.
531 $link = '<a href="' . admin_url( 'post.php?post=' . $field_group['ID'] . '&action=edit' ) . '" target="_blank">' . esc_html( $field_group['title'] ) . '</a>';
532
533 echo '' .
534 '<p><strong>' . __( 'Move Complete.', 'acf' ) . '</strong></p>' .
535 '<p>' . sprintf(
536 /* translators: Confirmation message once a field has been moved to a different field group. */
537 acf_punctify( __( 'The %1$s field can now be found in the %2$s field group', 'acf' ) ),
538 esc_html( $field['label'] ),
539 $link //phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
540 ) . '</p>' .
541 '<a href="#" class="button button-primary acf-close-popup">' . __( 'Close Modal', 'acf' ) . '</a>';
542 die();
543 }
544
545 // get all field groups.
546 $field_groups = acf_get_field_groups();
547 $choices = array();
548
549 // check.
550 if ( ! empty( $field_groups ) ) {
551
552 // loop.
553 foreach ( $field_groups as $field_group ) {
554
555 // bail early if no ID.
556 if ( ! $field_group['ID'] ) {
557 continue;
558 }
559
560 // bail early if is current.
561 if ( $field_group['ID'] == $args['post_id'] ) {
562 continue;
563 }
564
565 $choices[ $field_group['ID'] ] = $field_group['title'];
566 }
567 }
568
569 // render options.
570 $field = acf_get_valid_field(
571 array(
572 'type' => 'select',
573 'name' => 'acf_field_group',
574 'choices' => $choices,
575 'aria-label' => __( 'Please select the destination for this field', 'acf' ),
576 )
577 );
578
579 echo '<p>' . __( 'Please select the destination for this field', 'acf' ) . '</p>';
580
581 echo '<form id="acf-move-field-form">';
582
583 // render.
584 acf_render_field_wrap( $field );
585
586 echo '<button type="submit" class="acf-btn">' . __( 'Move Field', 'acf' ) . '</button>';
587
588 echo '</form>';
589
590 die();
591 }
592
593 }
594
595 // initialize.
596 new acf_admin_field_group();
597 endif; // Class exists check.
598
599 ?>
600