PluginProbe ʕ •ᴥ•ʔ
Secure Custom Fields / 6.4.0-beta2
Secure Custom Fields v6.4.0-beta2
6.9.5 6.9.4 6.9.3 6.9.2 6.9.1 6.9.0 6.8.9 6.8.7 6.8.8 6.8.6 6.8.4 6.8.5 trunk 6.4.0-beta1 6.4.0-beta2 6.4.1 6.4.1-beta3 6.4.1-beta4 6.4.1-beta5 6.4.1-beta6 6.4.1-beta7 6.4.2 6.5.0 6.5.1 6.5.2 6.5.3 6.5.4 6.5.5 6.5.6 6.5.7 6.6.0 6.7.0 6.7.1 6.8.0 6.8.1 6.8.2 6.8.3
secure-custom-fields / pro / fields / class-acf-field-clone.php
secure-custom-fields / pro / fields Last commit date
class-acf-field-clone.php 1 year ago class-acf-field-flexible-content.php 1 year ago class-acf-field-gallery.php 1 year ago class-acf-field-repeater.php 1 year ago class-acf-repeater-table.php 1 year ago index.php 1 year ago
class-acf-field-clone.php
1240 lines
1 <?php
2
3 if ( ! class_exists( 'acf_field_clone' ) ) :
4
5 class acf_field_clone extends acf_field {
6
7
8 /**
9 * This function will setup the field type data
10 *
11 * @type function
12 * @date 5/03/2014
13 * @since 5.0.0
14 *
15 * @param n/a
16 * @return n/a
17 */
18 function initialize() {
19
20 // vars
21 $this->name = 'clone';
22 $this->label = _x( 'Clone', 'noun', 'acf' );
23 $this->category = 'layout';
24 $this->description = __( 'Allows you to select and display existing fields. It does not duplicate any fields in the database, but loads and displays the selected fields at run-time. The Clone field can either replace itself with the selected fields or display the selected fields as a group of subfields.', 'acf' );
25 $this->preview_image = acf_get_url() . '/assets/images/field-type-previews/field-preview-clone.png';
26 $this->doc_url = 'https://www.advancedcustomfields.com/resources/clone/';
27 $this->tutorial_url = 'https://www.advancedcustomfields.com/resources/how-to-use-the-clone-field/';
28 $this->pro = true;
29 $this->supports = array( 'bindings' => false );
30 $this->defaults = array(
31 'clone' => '',
32 'prefix_label' => 0,
33 'prefix_name' => 0,
34 'display' => 'seamless',
35 'layout' => 'block',
36 );
37 $this->cloning = array();
38 $this->have_rows = 'single';
39
40 // register filter
41 acf_enable_filter( 'clone' );
42
43 // ajax
44 add_action( 'wp_ajax_acf/fields/clone/query', array( $this, 'ajax_query' ) );
45
46 // filters
47 add_filter( 'acf/get_fields', array( $this, 'acf_get_fields' ), 5, 2 );
48 add_filter( 'acf/prepare_field', array( $this, 'acf_prepare_field' ), 10, 1 );
49 add_filter( 'acf/clone_field', array( $this, 'acf_clone_field' ), 10, 2 );
50 }
51
52
53 /**
54 * This function will return true if acf_local functionality is enabled
55 *
56 * @type function
57 * @date 14/07/2016
58 * @since 5.4.0
59 *
60 * @param n/a
61 * @return n/a
62 */
63 function is_enabled() {
64
65 return acf_is_filter_enabled( 'clone' );
66 }
67
68
69 /**
70 * This filter is appied to the $field after it is loaded from the database
71 *
72 * @type filter
73 * @since 3.6
74 * @date 23/01/13
75 *
76 * @param $field - the field array holding all the field options
77 *
78 * @return $field - the field array holding all the field options
79 */
80 function load_field( $field ) {
81
82 // bail early if not enabled
83 if ( ! $this->is_enabled() ) {
84 return $field;
85 }
86
87 // load sub fields
88 // - sub field name's will be modified to include prefix_name settings
89 $field['sub_fields'] = $this->get_cloned_fields( $field );
90
91 // return
92 return $field;
93 }
94
95
96 /**
97 * This function will hook into the 'acf/get_fields' filter and inject/replace seamless clones fields
98 *
99 * @type function
100 * @date 17/06/2016
101 * @since 5.3.8
102 *
103 * @param $fields (array)
104 * @param $parent (array)
105 * @return $fields
106 */
107 function acf_get_fields( $fields, $parent ) {
108
109 // bail early if empty
110 if ( empty( $fields ) ) {
111 return $fields;
112 }
113
114 // bail early if not enabled
115 if ( ! $this->is_enabled() ) {
116 return $fields;
117 }
118
119 // vars
120 $i = 0;
121
122 // loop
123 while ( $i < count( $fields ) ) {
124
125 // vars
126 $field = $fields[ $i ];
127
128 // $i
129 ++$i;
130
131 // bail early if not a clone field
132 if ( $field['type'] != 'clone' ) {
133 continue;
134 }
135
136 // bail early if not seamless
137 if ( $field['display'] != 'seamless' ) {
138 continue;
139 }
140
141 // bail early if sub_fields isn't set or not an array
142 if ( ! isset( $field['sub_fields'] ) || ! is_array( $field['sub_fields'] ) ) {
143 continue;
144 }
145
146 // replace this clone field with sub fields
147 --$i;
148 array_splice( $fields, $i, 1, $field['sub_fields'] );
149 }
150
151 // return
152 return $fields;
153 }
154
155
156 /**
157 * This function will return an array of fields for a given clone field
158 *
159 * @type function
160 * @date 28/06/2016
161 * @since 5.3.8
162 *
163 * @param $field (array)
164 * @param $parent (array)
165 * @return (array)
166 */
167 function get_cloned_fields( $field ) {
168
169 // vars
170 $fields = array();
171
172 // bail early if no clone setting
173 if ( empty( $field['clone'] ) ) {
174 return $fields;
175 }
176
177 // bail early if already cloning this field (avoid infinite looping)
178 if ( isset( $this->cloning[ $field['key'] ] ) ) {
179 return $fields;
180 }
181
182 // update local ref
183 $this->cloning[ $field['key'] ] = 1;
184
185 // Loop over selectors and load fields.
186 foreach ( $field['clone'] as $selector ) {
187
188 // Field Group selector.
189 if ( acf_is_field_group_key( $selector ) ) {
190 $field_group = acf_get_field_group( $selector );
191 if ( ! $field_group ) {
192 continue;
193 }
194
195 $field_group_fields = acf_get_fields( $field_group );
196 if ( ! $field_group_fields ) {
197 continue;
198 }
199
200 $fields = array_merge( $fields, $field_group_fields );
201
202 // Field selector.
203 } elseif ( acf_is_field_key( $selector ) ) {
204 $fields[] = acf_get_field( $selector );
205 }
206 }
207
208 // field has ve been loaded for this $parent, time to remove cloning ref
209 unset( $this->cloning[ $field['key'] ] );
210
211 // clear false values (fields that don't exist)
212 $fields = array_filter( $fields );
213
214 // bail early if no sub fields
215 if ( empty( $fields ) ) {
216 return array();
217 }
218
219 // loop
220 // run acf_clone_field() on each cloned field to modify name, key, etc
221 foreach ( array_keys( $fields ) as $i ) {
222 $fields[ $i ] = acf_clone_field( $fields[ $i ], $field );
223 }
224
225 // return
226 return $fields;
227 }
228
229
230 /**
231 * This function is run when cloning a clone field
232 * Important to run the acf_clone_field function on sub fields to pass on settings such as 'parent_layout'
233 *
234 * @type function
235 * @date 28/06/2016
236 * @since 5.3.8
237 *
238 * @param $field (array)
239 * @param $clone_field (array)
240 * @return $field
241 */
242 function acf_clone_field( $field, $clone_field ) {
243
244 // bail early if this field is being cloned by some other kind of field (future proof)
245 if ( $clone_field['type'] != 'clone' ) {
246 return $field;
247 }
248
249 // backup (used later)
250 // - backup only once (cloned clone fields can cause issues)
251 if ( ! isset( $field['__key'] ) ) {
252 $field['__key'] = $field['key'];
253 $field['__name'] = $field['_name'];
254 $field['__label'] = $field['label'];
255 }
256
257 // seamless
258 if ( $clone_field['display'] == 'seamless' ) {
259
260 // modify key
261 // - this will allow sub clone fields to correctly load values for the same cloned field
262 // - the original key will later be restored by acf/prepare_field allowing conditional logic JS to work
263 $field['key'] = $clone_field['key'] . '_' . $field['key'];
264
265 // modify prefix allowing clone field to save sub fields
266 // - only used for parent seamless fields. Block or sub field's prefix will be overriden which also works
267 $field['prefix'] = $clone_field['prefix'] . '[' . $clone_field['key'] . ']';
268
269 // modify parent
270 $field['parent'] = $clone_field['parent'];
271
272 // label_format
273 if ( $clone_field['prefix_label'] ) {
274 $field['label'] = $clone_field['label'] . ' ' . $field['label'];
275 }
276 }
277
278 // prefix_name
279 if ( $clone_field['prefix_name'] ) {
280
281 // modify the field name
282 // - this will allow field to load / save correctly
283 $field['name'] = $clone_field['name'] . '_' . $field['_name'];
284
285 // modify the field _name (orig name)
286 // - this will allow fields to correctly understand the modified field
287 if ( $clone_field['display'] == 'seamless' ) {
288 $field['_name'] = $clone_field['_name'] . '_' . $field['_name'];
289 }
290 }
291
292 // required
293 if ( $clone_field['required'] ) {
294 $field['required'] = 1;
295 }
296
297 // type specific
298 // note: seamless clone fields will not be triggered
299 if ( $field['type'] == 'clone' ) {
300 $field = $this->acf_clone_clone_field( $field, $clone_field );
301 }
302
303 // return
304 return $field;
305 }
306
307
308 /**
309 * This function is run when cloning a clone field
310 * Important to run the acf_clone_field function on sub fields to pass on settings such as 'parent_layout'
311 * Do not delete! Removing this logic causes major issues with cloned clone fields within a flexible content layout.
312 *
313 * @type function
314 * @date 28/06/2016
315 * @since 5.3.8
316 *
317 * @param $field (array)
318 * @param $clone_field (array)
319 * @return $field
320 */
321 function acf_clone_clone_field( $field, $clone_field ) {
322
323 // modify the $clone_field name
324 // This seems odd, however, the $clone_field is later passed into the acf_clone_field() function
325 // Do not delete!
326 // when cloning a clone field, it is important to also change the _name too
327 // this allows sub clone fields to appear correctly in get_row() row array
328 if ( $field['prefix_name'] ) {
329 $clone_field['name'] = $field['_name'];
330 $clone_field['_name'] = $field['_name'];
331 }
332
333 // bail early if no sub fields
334 if ( empty( $field['sub_fields'] ) ) {
335 return $field;
336 }
337
338 // loop
339 foreach ( $field['sub_fields'] as &$sub_field ) {
340
341 // clone
342 $sub_field = acf_clone_field( $sub_field, $clone_field );
343 }
344
345 // return
346 return $field;
347 }
348
349
350 /**
351 * description
352 *
353 * @type function
354 * @date 4/11/16
355 * @since 5.5.0
356 *
357 * @param $post_id (int)
358 * @return $post_id (int)
359 */
360 function prepare_field_for_db( $field ) {
361
362 // bail early if no sub fields
363 if ( empty( $field['sub_fields'] ) ) {
364 return $field;
365 }
366
367 // bail early if name == _name
368 // this is a parent clone field and does not require any modification to sub field names
369 if ( $field['name'] == $field['_name'] ) {
370 return $field;
371 }
372
373 // this is a sub field
374 // _name = 'my_field'
375 // name = 'rep_0_my_field'
376 // modify all sub fields to add 'rep_0_' name prefix (prefix_name setting has already been applied)
377 $length = strlen( $field['_name'] );
378 $prefix = substr( $field['name'], 0, -$length );
379
380 // bail early if _name is not found at the end of name (unknown potential error)
381 if ( $prefix . $field['_name'] !== $field['name'] ) {
382 return $field;
383 }
384
385 // acf_log('== prepare_field_for_db ==');
386 // acf_log('- clone name:', $field['name']);
387 // acf_log('- clone _name:', $field['_name']);
388 // loop
389 foreach ( $field['sub_fields'] as &$sub_field ) {
390 $sub_field['name'] = $prefix . $sub_field['name'];
391 }
392
393 // return
394 return $field;
395 }
396
397
398 /**
399 * This filter is applied to the $value after it is loaded from the db
400 *
401 * @type filter
402 * @since 3.6
403 * @date 23/01/13
404 *
405 * @param $value (mixed) the value found in the database
406 * @param $post_id (mixed) the post_id from which the value was loaded
407 * @param $field (array) the field array holding all the field options
408 * @return $value
409 */
410 function load_value( $value, $post_id, $field ) {
411
412 // bail early if no sub fields
413 if ( empty( $field['sub_fields'] ) ) {
414 return $value;
415 }
416
417 // modify names
418 $field = $this->prepare_field_for_db( $field );
419
420 // load sub fields
421 $value = array();
422
423 // loop
424 foreach ( $field['sub_fields'] as $sub_field ) {
425
426 // add value
427 $value[ $sub_field['key'] ] = acf_get_value( $post_id, $sub_field );
428 }
429
430 // return
431 return $value;
432 }
433
434
435 /**
436 * This filter is appied to the $value after it is loaded from the db and before it is returned to the template
437 *
438 * @type filter
439 * @since 3.6
440 *
441 * @param mixed $value The value which was loaded from the database.
442 * @param mixed $post_id The $post_id from which the value was loaded.
443 * @param array $field The field array holding all the field options.
444 * @param boolean $escape_html Should the field return a HTML safe formatted value.
445 * @return mixed $value The modified value.
446 */
447 public function format_value( $value, $post_id, $field, $escape_html = false ) {
448
449 // bail early if no value
450 if ( empty( $value ) ) {
451 return false;
452 }
453
454 // modify names
455 $field = $this->prepare_field_for_db( $field );
456
457 // loop
458 foreach ( $field['sub_fields'] as $sub_field ) {
459
460 // extract value
461 $sub_value = acf_extract_var( $value, $sub_field['key'] );
462
463 // format value
464 $sub_value = acf_format_value( $sub_value, $post_id, $sub_field, $escape_html );
465
466 // append to $row
467 $value[ $sub_field['__name'] ] = $sub_value;
468 }
469
470 // return
471 return $value;
472 }
473
474 /**
475 * Apply basic formatting to prepare the value for default REST output.
476 *
477 * @param mixed $value
478 * @param string|integer $post_id
479 * @param array $field
480 * @return mixed
481 */
482 public function format_value_for_rest( $value, $post_id, array $field ) {
483 if ( empty( $value ) || ! is_array( $value ) ) {
484 return $value;
485 }
486
487 if ( ! is_array( $field ) || ! isset( $field['sub_fields'] ) || ! is_array( $field['sub_fields'] ) ) {
488 return $value;
489 }
490
491 // Loop through each row and within that, each sub field to process sub fields individually.
492 foreach ( $field['sub_fields'] as $sub_field ) {
493
494 // Extract the sub field 'field_key'=>'value' pair from the $value and format it.
495 $sub_value = acf_extract_var( $value, $sub_field['key'] );
496 $sub_value = acf_format_value_for_rest( $sub_value, $post_id, $sub_field );
497
498 // Add the sub field value back to the $value but mapped to the field name instead
499 // of the key reference.
500 $value[ $sub_field['name'] ] = $sub_value;
501 }
502
503 return $value;
504 }
505
506 /**
507 * This filter is appied to the $value before it is updated in the db
508 *
509 * @type filter
510 * @since 3.6
511 * @date 23/01/13
512 *
513 * @param $value - the value which will be saved in the database
514 * @param $field - the field array holding all the field options
515 * @param $post_id - the post_id of which the value will be saved
516 *
517 * @return $value - the modified value
518 */
519 function update_value( $value, $post_id, $field ) {
520
521 // bail early if no value
522 if ( ! acf_is_array( $value ) ) {
523 return null;
524 }
525
526 // bail early if no sub fields
527 if ( empty( $field['sub_fields'] ) ) {
528 return null;
529 }
530
531 // modify names
532 $field = $this->prepare_field_for_db( $field );
533
534 // loop
535 foreach ( $field['sub_fields'] as $sub_field ) {
536
537 // vars
538 $v = false;
539
540 // key (backend)
541 if ( isset( $value[ $sub_field['key'] ] ) ) {
542 $v = $value[ $sub_field['key'] ];
543
544 // name (frontend)
545 } elseif ( isset( $value[ $sub_field['_name'] ] ) ) {
546 $v = $value[ $sub_field['_name'] ];
547
548 // empty
549 } else {
550
551 // input is not set (hidden by conditioanl logic)
552 continue;
553 }
554
555 // restore original field key
556 $sub_field = $this->acf_prepare_field( $sub_field );
557
558 // update value
559 acf_update_value( $v, $post_id, $sub_field );
560 }
561
562 // return
563 return '';
564 }
565
566
567 /**
568 * Create the HTML interface for your field
569 *
570 * @param $field - an array holding all the field's data
571 *
572 * @type action
573 * @since 3.6
574 * @date 23/01/13
575 */
576 function render_field( $field ) {
577
578 // bail early if no sub fields
579 if ( empty( $field['sub_fields'] ) ) {
580 return;
581 }
582
583 // load values
584 foreach ( $field['sub_fields'] as &$sub_field ) {
585
586 // add value
587 if ( isset( $field['value'][ $sub_field['key'] ] ) ) {
588
589 // this is a normal value
590 $sub_field['value'] = $field['value'][ $sub_field['key'] ];
591 } elseif ( isset( $sub_field['default_value'] ) ) {
592
593 // no value, but this sub field has a default value
594 $sub_field['value'] = $sub_field['default_value'];
595 }
596
597 // update prefix to allow for nested values
598 $sub_field['prefix'] = $field['name'];
599
600 // restore label
601 $sub_field['label'] = $sub_field['__label'];
602
603 // restore required
604 if ( $field['required'] ) {
605 $sub_field['required'] = 0;
606 }
607 }
608
609 // render
610 if ( $field['layout'] == 'table' ) {
611 $this->render_field_table( $field );
612 } else {
613 $this->render_field_block( $field );
614 }
615 }
616
617
618 /**
619 * description
620 *
621 * @type function
622 * @date 12/07/2016
623 * @since 5.4.0
624 *
625 * @param $post_id (int)
626 * @return $post_id (int)
627 */
628 function render_field_block( $field ) {
629
630 // vars
631 $label_placement = $field['layout'] == 'block' ? 'top' : 'left';
632
633 // html
634 echo '<div class="acf-clone-fields acf-fields -' . esc_attr( $label_placement ) . ' -border">';
635
636 foreach ( $field['sub_fields'] as $sub_field ) {
637 acf_render_field_wrap( $sub_field );
638 }
639
640 echo '</div>';
641 }
642
643
644 /**
645 * description
646 *
647 * @type function
648 * @date 12/07/2016
649 * @since 5.4.0
650 *
651 * @param $post_id (int)
652 * @return $post_id (int)
653 */
654 function render_field_table( $field ) {
655
656 ?>
657 <table class="acf-table">
658 <thead>
659 <tr>
660 <?php
661 foreach ( $field['sub_fields'] as $sub_field ) :
662
663 // Prepare field (allow sub fields to be removed).
664 $sub_field = acf_prepare_field( $sub_field );
665 if ( ! $sub_field ) {
666 continue;
667 }
668
669 // Define attrs.
670 $attrs = array();
671 $attrs['class'] = 'acf-th';
672 $attrs['data-name'] = $sub_field['_name'];
673 $attrs['data-type'] = $sub_field['type'];
674 $attrs['data-key'] = $sub_field['key'];
675
676 if ( $sub_field['wrapper']['width'] ) {
677 $attrs['data-width'] = $sub_field['wrapper']['width'];
678 $attrs['style'] = 'width: ' . $sub_field['wrapper']['width'] . '%;';
679 }
680
681 ?>
682 <th <?php echo acf_esc_attrs( $attrs ); ?>>
683 <?php acf_render_field_label( $sub_field ); ?>
684 <?php acf_render_field_instructions( $sub_field ); ?>
685 </th>
686 <?php endforeach; ?>
687 </tr>
688 </thead>
689 <tbody>
690 <tr class="acf-row">
691 <?php
692
693 foreach ( $field['sub_fields'] as $sub_field ) {
694 acf_render_field_wrap( $sub_field, 'td' );
695 }
696
697 ?>
698 </tr>
699 </tbody>
700 </table>
701 <?php
702 }
703
704
705 /**
706 * Create extra options for your field. This is rendered when editing a field.
707 * The value of $field['name'] can be used (like bellow) to save extra data to the $field
708 *
709 * @param $field - an array holding all the field's data
710 *
711 * @type action
712 * @since 3.6
713 * @date 23/01/13
714 */
715 function render_field_settings( $field ) {
716
717 // temp enable 'local' to allow .json fields to be displayed
718 acf_enable_filter( 'local' );
719
720 // default_value
721 acf_render_field_setting(
722 $field,
723 array(
724 'label' => __( 'Fields', 'acf' ),
725 'instructions' => __( 'Select one or more fields you wish to clone', 'acf' ),
726 'type' => 'select',
727 'name' => 'clone',
728 'multiple' => 1,
729 'allow_null' => 1,
730 'choices' => $this->get_clone_setting_choices( $field['clone'] ),
731 'ui' => 1,
732 'ajax' => 1,
733 'ajax_action' => 'acf/fields/clone/query',
734 'placeholder' => '',
735 'nonce' => wp_create_nonce( 'acf/fields/clone/query' ),
736 )
737 );
738
739 acf_disable_filter( 'local' );
740
741 // display
742 acf_render_field_setting(
743 $field,
744 array(
745 'label' => __( 'Display', 'acf' ),
746 'instructions' => __( 'Specify the style used to render the clone field', 'acf' ),
747 'type' => 'select',
748 'name' => 'display',
749 'class' => 'setting-display',
750 'choices' => array(
751 'group' => __( 'Group (displays selected fields in a group within this field)', 'acf' ),
752 'seamless' => __( 'Seamless (replaces this field with selected fields)', 'acf' ),
753 ),
754 )
755 );
756
757 // layout
758 acf_render_field_setting(
759 $field,
760 array(
761 'label' => __( 'Layout', 'acf' ),
762 'instructions' => __( 'Specify the style used to render the selected fields', 'acf' ),
763 'type' => 'radio',
764 'name' => 'layout',
765 'layout' => 'horizontal',
766 'choices' => array(
767 'block' => __( 'Block', 'acf' ),
768 'table' => __( 'Table', 'acf' ),
769 'row' => __( 'Row', 'acf' ),
770 ),
771 )
772 );
773
774 // prefix_label
775 $instructions = __( 'Labels will be displayed as %s', 'acf' );
776 $instructions = sprintf( $instructions, '<code class="prefix-label-code-1"></code>' );
777 acf_render_field_setting(
778 $field,
779 array(
780 'label' => __( 'Prefix Field Labels', 'acf' ),
781 'instructions' => $instructions,
782 'name' => 'prefix_label',
783 'class' => 'setting-prefix-label',
784 'type' => 'true_false',
785 'ui' => 1,
786 )
787 );
788
789 // prefix_name
790 $instructions = __( 'Values will be saved as %s', 'acf' );
791 $instructions = sprintf( $instructions, '<code class="prefix-name-code-1"></code>' );
792 acf_render_field_setting(
793 $field,
794 array(
795 'label' => __( 'Prefix Field Names', 'acf' ),
796 'instructions' => $instructions,
797 'name' => 'prefix_name',
798 'class' => 'setting-prefix-name',
799 'type' => 'true_false',
800 'ui' => 1,
801 )
802 );
803 }
804
805
806 /**
807 * This function will return an array of choices data for Select2
808 *
809 * @type function
810 * @date 17/06/2016
811 * @since 5.3.8
812 *
813 * @param $value (mixed)
814 * @return (array)
815 */
816 function get_clone_setting_choices( $value ) {
817
818 // vars
819 $choices = array();
820
821 // bail early if no $value
822 if ( empty( $value ) ) {
823 return $choices;
824 }
825
826 // force value to array
827 $value = acf_get_array( $value );
828
829 // loop
830 foreach ( $value as $v ) {
831 $choices[ $v ] = $this->get_clone_setting_choice( $v );
832 }
833
834 // return
835 return $choices;
836 }
837
838
839 /**
840 * This function will return the label for a given clone choice
841 *
842 * @type function
843 * @date 17/06/2016
844 * @since 5.3.8
845 *
846 * @param $selector (mixed)
847 * @return (string)
848 */
849 function get_clone_setting_choice( $selector = '' ) {
850
851 // bail early no selector
852 if ( ! $selector ) {
853 return '';
854 }
855
856 // phpcs:disable WordPress.Security.NonceVerification.Missing -- Verified elsewhere.
857 // ajax_fields
858 if ( isset( $_POST['fields'][ $selector ] ) ) {
859 return $this->get_clone_setting_field_choice( acf_sanitize_request_args( $_POST['fields'][ $selector ] ) );
860 }
861 // phpcs:enable WordPress.Security.NonceVerification.Missing
862
863 // field
864 if ( acf_is_field_key( $selector ) ) {
865 return $this->get_clone_setting_field_choice( acf_get_field( $selector ) );
866 }
867
868 // group
869 if ( acf_is_field_group_key( $selector ) ) {
870 return $this->get_clone_setting_group_choice( acf_get_field_group( $selector ) );
871 }
872
873 // return
874 return $selector;
875 }
876
877
878 /**
879 * This function will return the text for a field choice
880 *
881 * @type function
882 * @date 20/07/2016
883 * @since 5.4.0
884 *
885 * @param $field (array)
886 * @return (string)
887 */
888 function get_clone_setting_field_choice( $field ) {
889
890 // bail early if no field
891 if ( ! $field ) {
892 return __( 'Unknown field', 'acf' );
893 }
894
895 // title
896 $title = $field['label'] ? $field['label'] : __( '(no title)', 'acf' );
897
898 // append type
899 $title .= ' (' . $field['type'] . ')';
900
901 // ancestors
902 // - allow for AJAX to send through ancestors count
903 $ancestors = isset( $field['ancestors'] ) ? $field['ancestors'] : count( acf_get_field_ancestors( $field ) );
904 $title = str_repeat( '- ', $ancestors ) . $title;
905
906 // return
907 return $title;
908 }
909
910
911 /**
912 * This function will return the text for a group choice
913 *
914 * @type function
915 * @date 20/07/2016
916 * @since 5.4.0
917 *
918 * @param $field_group (array)
919 * @return (string)
920 */
921 function get_clone_setting_group_choice( $field_group ) {
922
923 // bail early if no field group
924 if ( ! $field_group ) {
925 return __( 'Unknown field group', 'acf' );
926 }
927
928 // return
929 return sprintf( __( 'All fields from %s field group', 'acf' ), $field_group['title'] );
930 }
931
932
933 /**
934 * AJAX handler for getting potential fields to clone.
935 *
936 * @since 5.3.8
937 *
938 * @return void
939 */
940 public function ajax_query() {
941 $nonce = acf_request_arg( 'nonce', '' );
942
943 if ( ! acf_verify_ajax( $nonce, 'acf/fields/clone/query' ) ) {
944 die();
945 }
946
947 // disable field to allow clone fields to appear selectable
948 acf_disable_filter( 'clone' );
949
950 // options
951 $options = acf_parse_args(
952 $_POST,
953 array(
954 'post_id' => 0,
955 'paged' => 0,
956 's' => '',
957 'title' => '',
958 'fields' => array(),
959 )
960 );
961
962 // vars
963 $results = array();
964 $s = false;
965 $i = -1;
966 $limit = 20;
967 $range_start = $limit * ( $options['paged'] - 1 ); // 0, 20, 40
968 $range_end = $range_start + ( $limit - 1 ); // 19, 39, 59
969
970 // search
971 if ( $options['s'] !== '' ) {
972
973 // strip slashes (search may be integer)
974 $s = wp_unslash( strval( $options['s'] ) );
975 }
976
977 // load groups
978 $field_groups = acf_get_field_groups();
979 $field_group = false;
980
981 // bail early if no field groups
982 if ( empty( $field_groups ) ) {
983 die();
984 }
985
986 // move current field group to start
987 foreach ( array_keys( $field_groups ) as $j ) {
988
989 // check ID
990 if ( $field_groups[ $j ]['ID'] !== $options['post_id'] ) {
991 continue;
992 }
993
994 // extract field group and move to start
995 $field_group = acf_extract_var( $field_groups, $j );
996
997 // field group found, stop looking
998 break;
999 }
1000
1001 // if field group was not found, this is a new field group (not yet saved)
1002 if ( ! $field_group ) {
1003 $field_group = array(
1004 'ID' => $options['post_id'],
1005 'title' => $options['title'],
1006 'key' => '',
1007 );
1008 }
1009
1010 // move current field group to start of list
1011 array_unshift( $field_groups, $field_group );
1012
1013 // loop
1014 foreach ( $field_groups as $field_group ) {
1015
1016 // vars
1017 $fields = false;
1018 $ignore_s = false;
1019 $data = array(
1020 'text' => $field_group['title'],
1021 'children' => array(),
1022 );
1023
1024 // get fields
1025 if ( $field_group['ID'] == $options['post_id'] ) {
1026 $fields = $options['fields'];
1027 } else {
1028 $fields = acf_get_fields( $field_group );
1029 $fields = acf_prepare_fields_for_import( $fields );
1030 }
1031
1032 // bail early if no fields
1033 if ( ! $fields ) {
1034 continue;
1035 }
1036
1037 // show all children for field group search match
1038 if ( $s !== false && stripos( $data['text'], $s ) !== false ) {
1039 $ignore_s = true;
1040 }
1041
1042 // populate children
1043 $children = array();
1044 $children[] = $field_group['key'];
1045 foreach ( $fields as $field ) {
1046 $children[] = $field['key']; }
1047
1048 // loop
1049 foreach ( $children as $child ) {
1050
1051 // bail early if no key (fake field group or corrupt field)
1052 if ( ! $child ) {
1053 continue;
1054 }
1055
1056 // vars
1057 $text = false;
1058
1059 // bail early if is search, and $text does not contain $s
1060 if ( $s !== false && ! $ignore_s ) {
1061
1062 // get early
1063 $text = $this->get_clone_setting_choice( $child );
1064
1065 // search
1066 if ( stripos( $text, $s ) === false ) {
1067 continue;
1068 }
1069 }
1070
1071 // $i
1072 ++$i;
1073
1074 // bail early if $i is out of bounds
1075 if ( $i < $range_start || $i > $range_end ) {
1076 continue;
1077 }
1078
1079 // load text
1080 if ( $text === false ) {
1081 $text = $this->get_clone_setting_choice( $child );
1082 }
1083
1084 // append
1085 $data['children'][] = array(
1086 'id' => $child,
1087 'text' => $text,
1088 );
1089 }
1090
1091 // bail early if no children
1092 // - this group contained fields, but none shown on this page
1093 if ( empty( $data['children'] ) ) {
1094 continue;
1095 }
1096
1097 // append
1098 $results[] = $data;
1099
1100 // end loop if $i is out of bounds
1101 // - no need to look further
1102 if ( $i > $range_end ) {
1103 break;
1104 }
1105 }
1106
1107 // return
1108 acf_send_ajax_results(
1109 array(
1110 'results' => $results,
1111 'limit' => $limit,
1112 )
1113 );
1114 }
1115
1116
1117 /**
1118 * This function will restore a field's key ready for input
1119 *
1120 * @type function
1121 * @date 6/09/2016
1122 * @since 5.4.0
1123 *
1124 * @param $field (array)
1125 * @return $field
1126 */
1127 function acf_prepare_field( $field ) {
1128
1129 // bail early if not cloned
1130 if ( empty( $field['_clone'] ) ) {
1131 return $field;
1132 }
1133
1134 // restore key
1135 if ( isset( $field['__key'] ) ) {
1136 $field['key'] = $field['__key'];
1137 }
1138
1139 // return
1140 return $field;
1141 }
1142
1143
1144 /**
1145 * description
1146 *
1147 * @type function
1148 * @date 11/02/2014
1149 * @since 5.0.0
1150 *
1151 * @param $post_id (int)
1152 * @return $post_id (int)
1153 */
1154 function validate_value( $valid, $value, $field, $input ) {
1155
1156 // bail early if no $value
1157 if ( empty( $value ) ) {
1158 return $valid;
1159 }
1160
1161 // bail early if no sub fields
1162 if ( empty( $field['sub_fields'] ) ) {
1163 return $valid;
1164 }
1165
1166 // loop
1167 foreach ( array_keys( $field['sub_fields'] ) as $i ) {
1168
1169 // get sub field
1170 $sub_field = $field['sub_fields'][ $i ];
1171 $k = $sub_field['key'];
1172
1173 // bail early if valu enot set (conditional logic?)
1174 if ( ! isset( $value[ $k ] ) ) {
1175 continue;
1176 }
1177
1178 // validate
1179 acf_validate_value( $value[ $k ], $sub_field, "{$input}[{$k}]" );
1180 }
1181
1182 // return
1183 return $valid;
1184 }
1185
1186 /**
1187 * Return the schema array for the REST API.
1188 *
1189 * @param array $field
1190 * @return array
1191 */
1192 public function get_rest_schema( array $field ) {
1193 $schema = array(
1194 'type' => array( 'object', 'null' ),
1195 'required' => ! empty( $field['required'] ) ? array() : false,
1196 'items' => array(
1197 'type' => 'object',
1198 'properties' => array(),
1199 ),
1200 );
1201
1202 foreach ( $field['sub_fields'] as $sub_field ) {
1203 /** @var acf_field $type */
1204 $type = acf_get_field_type( $sub_field['type'] );
1205
1206 if ( ! $type ) {
1207 continue;
1208 }
1209
1210 $sub_field_schema = $type->get_rest_schema( $sub_field );
1211
1212 // Passing null to nested fields has no effect. Remove this as a possible type to prevent
1213 // confusion in the schema.
1214 $null_type_index = array_search( 'null', $sub_field_schema['type'] );
1215 if ( $null_type_index !== false ) {
1216 unset( $sub_field_schema['type'][ $null_type_index ] );
1217 }
1218
1219 $schema['items']['properties'][ $sub_field['name'] ] = $sub_field_schema;
1220
1221 /**
1222 * If the clone field itself is marked as required, all subfields are required,
1223 * regardless of the status of the original fields.
1224 */
1225 if ( is_array( $schema['required'] ) ) {
1226 $schema['required'][] = $sub_field['name'];
1227 }
1228 }
1229
1230 return $schema;
1231 }
1232 }
1233
1234
1235 // initialize
1236 acf_register_field_type( 'acf_field_clone' );
1237 endif; // class_exists check
1238
1239 ?>
1240