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