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-flexible-content.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-flexible-content.php
1700 lines
1 <?php
2
3 if ( ! class_exists( 'acf_field_flexible_content' ) ) :
4
5 class acf_field_flexible_content 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 public function initialize() {
19
20 // vars
21 $this->name = 'flexible_content';
22 $this->label = __( 'Flexible Content', 'acf' );
23 $this->category = 'layout';
24 $this->description = __( 'Allows you to define, create and manage content with total control by creating layouts that contain subfields that content editors can choose from.', 'acf' ) . ' ' . __( 'We do not recommend using this field in ACF Blocks.', 'acf' );
25 $this->preview_image = acf_get_url() . '/assets/images/field-type-previews/field-preview-flexible-content.png';
26 $this->doc_url = 'https://www.advancedcustomfields.com/resources/flexible-content/';
27 $this->tutorial_url = 'https://www.advancedcustomfields.com/resources/building-layouts-with-the-flexible-content-field-in-a-theme/';
28 $this->pro = true;
29 $this->supports = array( 'bindings' => false );
30 $this->defaults = array(
31 'layouts' => array(),
32 'min' => '',
33 'max' => '',
34 'button_label' => __( 'Add Row', 'acf' ),
35 );
36
37 // ajax
38 $this->add_action( 'wp_ajax_acf/fields/flexible_content/layout_title', array( $this, 'ajax_layout_title' ) );
39 $this->add_action( 'wp_ajax_nopriv_acf/fields/flexible_content/layout_title', array( $this, 'ajax_layout_title' ) );
40
41 // filters
42 $this->add_filter( 'acf/prepare_field_for_export', array( $this, 'prepare_any_field_for_export' ) );
43 $this->add_filter( 'acf/clone_field', array( $this, 'clone_any_field' ), 10, 2 );
44 $this->add_filter( 'acf/validate_field', array( $this, 'validate_any_field' ) );
45
46 // field filters
47 $this->add_field_filter( 'acf/get_sub_field', array( $this, 'get_sub_field' ), 10, 3 );
48 $this->add_field_filter( 'acf/prepare_field_for_export', array( $this, 'prepare_field_for_export' ) );
49 $this->add_field_filter( 'acf/prepare_field_for_import', array( $this, 'prepare_field_for_import' ) );
50 }
51
52
53 /**
54 * Admin scripts enqueue for field.
55 *
56 * @type function
57 * @date 16/12/2015
58 * @since 5.3.2
59 *
60 * @param $post_id (int)
61 * @return $post_id (int)
62 */
63 public function input_admin_enqueue_scripts() {
64
65 // localize
66 acf_localize_text(
67 array(
68
69 // identifiers
70 'layout' => __( 'layout', 'acf' ),
71 'layouts' => __( 'layouts', 'acf' ),
72 'Fields' => __( 'Fields', 'acf' ),
73
74 // min / max
75 'This field requires at least {min} {label} {identifier}' => __( 'This field requires at least {min} {label} {identifier}', 'acf' ),
76 'This field has a limit of {max} {label} {identifier}' => __( 'This field has a limit of {max} {label} {identifier}', 'acf' ),
77
78 // popup badge
79 '{available} {label} {identifier} available (max {max})' => __( '{available} {label} {identifier} available (max {max})', 'acf' ),
80 '{required} {label} {identifier} required (min {min})' => __( '{required} {label} {identifier} required (min {min})', 'acf' ),
81
82 // field settings
83 'Flexible Content requires at least 1 layout' => __( 'Flexible Content requires at least 1 layout', 'acf' ),
84 )
85 );
86 }
87
88
89 /**
90 * This function will fill in the missing keys to create a valid layout
91 *
92 * @type function
93 * @date 3/10/13
94 * @since 1.1.0
95 *
96 * @param $layout (array)
97 * @return $layout (array)
98 */
99 function get_valid_layout( $layout = array() ) {
100
101 // parse
102 $layout = wp_parse_args(
103 $layout,
104 array(
105 'key' => uniqid( 'layout_' ),
106 'name' => '',
107 'label' => '',
108 'display' => 'block',
109 'sub_fields' => array(),
110 'min' => '',
111 'max' => '',
112 )
113 );
114
115 // return
116 return $layout;
117 }
118
119
120 /**
121 * This filter is appied to the $field after it is loaded from the database
122 *
123 * @type filter
124 * @since 3.6
125 * @date 23/01/13
126 *
127 * @param $field - the field array holding all the field options
128 *
129 * @return $field - the field array holding all the field options
130 */
131 function load_field( $field ) {
132
133 // bail early if no field layouts
134 if ( empty( $field['layouts'] ) ) {
135 return $field;
136 }
137
138 // vars
139 $sub_fields = acf_get_fields( $field );
140
141 // loop through layouts, sub fields and swap out the field key with the real field
142 foreach ( array_keys( $field['layouts'] ) as $i ) {
143
144 // extract layout
145 $layout = acf_extract_var( $field['layouts'], $i );
146
147 // validate layout
148 $layout = $this->get_valid_layout( $layout );
149
150 // append sub fields
151 if ( ! empty( $sub_fields ) ) {
152 foreach ( array_keys( $sub_fields ) as $k ) {
153
154 // check if 'parent_layout' is empty
155 if ( empty( $sub_fields[ $k ]['parent_layout'] ) ) {
156
157 // parent_layout did not save for this field, default it to first layout
158 $sub_fields[ $k ]['parent_layout'] = $layout['key'];
159 }
160
161 // append sub field to layout,
162 if ( $sub_fields[ $k ]['parent_layout'] == $layout['key'] ) {
163 $layout['sub_fields'][] = acf_extract_var( $sub_fields, $k );
164 }
165 }
166 }
167
168 // append back to layouts
169 $field['layouts'][ $i ] = $layout;
170 }
171
172 // return
173 return $field;
174 }
175
176
177 /**
178 * This function will return a specific sub field
179 *
180 * @type function
181 * @date 29/09/2016
182 * @since 5.4.0
183 *
184 * @param $sub_field
185 * @param $selector (string)
186 * @param $field (array)
187 * @return $post_id (int)
188 */
189 function get_sub_field( $sub_field, $id, $field ) {
190
191 // Get active layout.
192 $active = get_row_layout();
193
194 // Loop over layouts.
195 if ( $field['layouts'] ) {
196 foreach ( $field['layouts'] as $layout ) {
197
198 // Restict to active layout if within a have_rows() loop.
199 if ( $active && $active !== $layout['name'] ) {
200 continue;
201 }
202
203 // Check sub fields.
204 if ( $layout['sub_fields'] ) {
205 $sub_field = acf_search_fields( $id, $layout['sub_fields'] );
206 if ( $sub_field ) {
207 break;
208 }
209 }
210 }
211 }
212
213 // return
214 return $sub_field;
215 }
216
217
218 /**
219 * Create the HTML interface for your field
220 *
221 * @param $field - an array holding all the field's data
222 *
223 * @type action
224 * @since 3.6
225 * @date 23/01/13
226 */
227 function render_field( $field ) {
228
229 // defaults
230 if ( empty( $field['button_label'] ) ) {
231 $field['button_label'] = $this->defaults['button_label'];
232 }
233
234 // sort layouts into names
235 $layouts = array();
236
237 foreach ( $field['layouts'] as $k => $layout ) {
238 $layouts[ $layout['name'] ] = $layout;
239 }
240
241 // vars
242 $div = array(
243 'class' => 'acf-flexible-content',
244 'data-min' => $field['min'],
245 'data-max' => $field['max'],
246 );
247
248 // empty
249 if ( empty( $field['value'] ) ) {
250 $div['class'] .= ' -empty';
251 }
252
253 // no value message
254 // translators: %s the button label used for adding a new layout.
255 $no_value_message = __( 'Click the "%s" button below to start creating your layout', 'acf' );
256 $no_value_message = apply_filters( 'acf/fields/flexible_content/no_value_message', $no_value_message, $field );
257 $no_value_message = sprintf( $no_value_message, $field['button_label'] );
258
259 ?>
260 <div <?php echo acf_esc_attrs( $div ); ?>>
261
262 <?php acf_hidden_input( array( 'name' => $field['name'] ) ); ?>
263
264 <div class="no-value-message">
265 <?php echo acf_esc_html( $no_value_message ); ?>
266 </div>
267
268 <div class="clones">
269 <?php foreach ( $layouts as $layout ) : ?>
270 <?php $this->render_layout( $field, $layout, 'acfcloneindex', array() ); ?>
271 <?php endforeach; ?>
272 </div>
273
274 <div class="values">
275 <?php
276 if ( ! empty( $field['value'] ) ) {
277 foreach ( $field['value'] as $i => $value ) {
278
279 // validate
280 if ( ! is_array( $value ) ) {
281 continue;
282 }
283
284 if ( empty( $layouts[ $value['acf_fc_layout'] ] ) ) {
285 continue;
286 }
287
288 // render
289 $this->render_layout( $field, $layouts[ $value['acf_fc_layout'] ], $i, $value );
290 }
291 }
292 ?>
293 </div>
294
295 <div class="acf-actions">
296 <a class="acf-button button button-primary" href="#" data-name="add-layout"><?php echo acf_esc_html( $field['button_label'] ); ?></a>
297 </div>
298
299 <?php
300 echo '<script type="text-html" class="tmpl-popup"><ul>';
301 foreach ( $layouts as $layout ) {
302 $atts = array(
303 'href' => '#',
304 'data-layout' => $layout['name'],
305 'data-min' => $layout['min'],
306 'data-max' => $layout['max'],
307 );
308 printf( '<li><a %s>%s</a></li>', acf_esc_attrs( $atts ), acf_esc_html( $layout['label'] ) );
309 }
310 echo '</ul></script>';
311 ?>
312 </div>
313 <?php
314 }
315
316
317 /**
318 * description
319 *
320 * @type function
321 * @date 19/11/2013
322 * @since 5.0.0
323 *
324 * @param $post_id (int)
325 * @return $post_id (int)
326 */
327 function render_layout( $field, $layout, $i, $value ) {
328
329 // vars
330 $order = 0;
331 $el = 'div';
332 $sub_fields = $layout['sub_fields'];
333 $id = ( $i === 'acfcloneindex' ) ? 'acfcloneindex' : "row-$i";
334 $prefix = $field['name'] . '[' . $id . ']';
335
336 // div
337 $div = array(
338 'class' => 'layout',
339 'data-id' => $id,
340 'data-layout' => $layout['name'],
341 'data-max' => $layout['max'],
342 'data-label' => $layout['label'],
343 );
344
345 // clone
346 if ( is_numeric( $i ) ) {
347 $order = $i + 1;
348 } else {
349 $div['class'] .= ' acf-clone';
350 }
351
352 // display
353 if ( $layout['display'] == 'table' ) {
354 $el = 'td';
355 }
356
357 // title
358 $title = $this->get_layout_title( $field, $layout, $i, $value );
359
360 // remove row
361 reset_rows();
362
363 ?>
364 <div <?php echo acf_esc_attrs( $div ); ?>>
365
366 <?php
367 acf_hidden_input(
368 array(
369 'name' => $prefix . '[acf_fc_layout]',
370 'value' => $layout['name'],
371 )
372 );
373 ?>
374
375 <div class="acf-fc-layout-handle" title="<?php esc_attr_e( 'Drag to reorder', 'acf' ); ?>" data-name="collapse-layout"><?php echo acf_esc_html( $title ); ?></div>
376
377 <div class="acf-fc-layout-controls">
378 <a class="acf-icon -plus small light acf-js-tooltip" href="#" data-name="add-layout" title="<?php esc_attr_e( 'Add layout', 'acf' ); ?>"></a>
379 <a class="acf-icon -duplicate small light acf-js-tooltip" href="#" data-name="duplicate-layout" title="<?php esc_attr_e( 'Duplicate layout', 'acf' ); ?>"></a>
380 <a class="acf-icon -minus small light acf-js-tooltip" href="#" data-name="remove-layout" title="<?php esc_attr_e( 'Remove layout', 'acf' ); ?>"></a>
381 <a class="acf-icon -collapse small -clear acf-js-tooltip" href="#" data-name="collapse-layout" title="<?php esc_attr_e( 'Click to toggle', 'acf' ); ?>"></a>
382 </div>
383
384 <?php if ( ! empty( $sub_fields ) ) : ?>
385
386 <?php if ( $layout['display'] == 'table' ) : ?>
387 <table class="acf-table">
388
389 <thead>
390 <tr>
391 <?php
392 foreach ( $sub_fields as $sub_field ) :
393
394 // Set prefix to generate correct "for" attribute on <label>.
395 $sub_field['prefix'] = $prefix;
396
397 // Prepare field (allow sub fields to be removed).
398 $sub_field = acf_prepare_field( $sub_field );
399 if ( ! $sub_field ) {
400 continue;
401 }
402
403 // Define attrs.
404 $attrs = array();
405 $attrs['class'] = 'acf-th';
406 $attrs['data-name'] = $sub_field['_name'];
407 $attrs['data-type'] = $sub_field['type'];
408 $attrs['data-key'] = $sub_field['key'];
409
410 if ( $sub_field['wrapper']['width'] ) {
411 $attrs['data-width'] = $sub_field['wrapper']['width'];
412 $attrs['style'] = 'width: ' . $sub_field['wrapper']['width'] . '%;';
413 }
414
415 ?>
416 <th <?php echo acf_esc_attrs( $attrs ); ?>>
417 <?php acf_render_field_label( $sub_field ); ?>
418 <?php acf_render_field_instructions( $sub_field ); ?>
419 </th>
420 <?php endforeach; ?>
421 </tr>
422 </thead>
423
424 <tbody>
425 <tr class="acf-row">
426 <?php else : ?>
427 <div class="acf-fields
428 <?php
429 if ( $layout['display'] == 'row' ) :
430 ?>
431 -left<?php endif; ?>">
432 <?php endif; ?>
433
434 <?php
435
436 // loop though sub fields
437 foreach ( $sub_fields as $sub_field ) {
438
439 // add value
440 if ( isset( $value[ $sub_field['key'] ] ) ) {
441
442 // this is a normal value
443 $sub_field['value'] = $value[ $sub_field['key'] ];
444 } elseif ( isset( $sub_field['default_value'] ) ) {
445
446 // no value, but this sub field has a default value
447 $sub_field['value'] = $sub_field['default_value'];
448 }
449
450 // update prefix to allow for nested values
451 $sub_field['prefix'] = $prefix;
452
453 // render input
454 acf_render_field_wrap( $sub_field, $el );
455 }
456
457 ?>
458
459 <?php if ( $layout['display'] == 'table' ) : ?>
460 </tr>
461 </tbody>
462 </table>
463 <?php else : ?>
464 </div>
465 <?php endif; ?>
466
467 <?php endif; ?>
468
469 </div>
470 <?php
471 }
472
473 /**
474 * Renders the flexible content field layouts in the field group editor.
475 *
476 * @since 3.6
477 * @date 23/01/13
478 *
479 * @param array $field An array holding all the field's data.
480 */
481 public function render_field_settings( $field ) {
482 $layout_open = apply_filters( 'acf/fields/flexible_content/layout_default_expanded', false );
483
484 // Load default layout.
485 if ( empty( $field['layouts'] ) ) {
486 $layout_open = true;
487 $field['layouts'] = array(
488 array(),
489 );
490 }
491
492 $field_settings_class = $layout_open ? 'open' : '';
493 $toggle_class = $layout_open ? 'open' : 'closed';
494 $field_settings_style = $layout_open ? '' : 'display: none;';
495
496 // loop through layouts
497 foreach ( $field['layouts'] as $layout ) {
498
499 // get valid layout
500 $layout = $this->get_valid_layout( $layout );
501
502 // vars
503 $layout_prefix = "{$field['prefix']}[layouts][{$layout['key']}]";
504
505 ?>
506 <div class="acf-field acf-field-setting-fc_layout" data-name="fc_layout" data-setting="flexible_content" data-layout-label="<?php echo esc_attr( $layout['label'] ); ?>" data-layout-name="<?php echo esc_attr( $layout['name'] ); ?>" data-id="<?php echo esc_attr( $layout['key'] ); ?>">
507 <div class="acf-label acf-field-settings-fc_head">
508 <div class="acf-fc_draggable">
509 <label class="acf-fc-layout-label reorder-layout"><?php esc_attr_e( 'Layout', 'acf' ); ?></label>
510 </div>
511
512 <div class="acf-fc-layout-name copyable">
513 <span class="layout-name"></span>
514 </div>
515
516 <ul class="acf-bl acf-fl-actions">
517 <li><button class="acf-btn acf-btn-tertiary acf-btn-sm acf-field-setting-fc-delete"><i class="acf-icon acf-icon-trash delete-layout " href="#" title="<?php esc_attr_e( 'Delete Layout', 'acf' ); ?>"></i></button></li>
518 <li><button class="acf-btn acf-btn-tertiary acf-btn-sm acf-field-setting-fc-duplicate"><i class="acf-icon -duplicate duplicate-layout" href="#" title="<?php esc_attr_e( 'Duplicate Layout', 'acf' ); ?>"></i></button></li>
519 <li class="acf-fc-add-layout"><button class="add-layout acf-btn acf-btn-primary add-field" href="#" title="<?php esc_attr_e( 'Add New Layout', 'acf' ); ?>"><i class="acf-icon acf-icon-plus"></i><?php esc_html_e( 'Add Layout', 'acf' ); ?></button></li>
520 <li><button type="button" class="acf-toggle-fc-layout" aria-expanded="true"></li>
521 <li><span class="toggle-indicator <?php echo esc_attr( $toggle_class ); ?>" aria-hidden="true"></span></li>
522 </ul>
523 </div>
524 <div class="acf-input acf-field-layout-settings <?php echo esc_attr( $field_settings_class ); ?>" style="<?php echo esc_attr( $field_settings_style ); ?>">
525 <?php
526
527 acf_hidden_input(
528 array(
529 'id' => acf_idify( $layout_prefix . '[key]' ),
530 'name' => $layout_prefix . '[key]',
531 'class' => 'layout-key',
532 'value' => $layout['key'],
533 )
534 );
535
536 ?>
537 <ul class="acf-fc-meta acf-bl">
538 <li class="acf-fc-meta-label acf-fc-meta-left">
539 <?php
540
541 acf_render_field(
542 array(
543 'type' => 'text',
544 'name' => 'label',
545 'class' => 'layout-label',
546 'prefix' => $layout_prefix,
547 'value' => $layout['label'],
548 'prepend' => __( 'Label', 'acf' ),
549 )
550 );
551
552 ?>
553 </li>
554 <li class="acf-fc-meta-name acf-fc-meta-right copyable input-copyable">
555 <?php
556
557 acf_render_field(
558 array(
559 'type' => 'text',
560 'name' => 'name',
561 'class' => 'layout-name',
562 'input-data' => array( '1p-ignore' => 'true' ),
563 'prefix' => $layout_prefix,
564 'value' => $layout['name'],
565 'prepend' => __( 'Name', 'acf' ),
566 )
567 );
568
569 ?>
570 </li>
571 <li class="acf-fc-meta-display acf-fc-meta-left">
572 <div class="acf-input-prepend"><?php esc_html_e( 'Layout', 'acf' ); ?></div>
573 <div class="acf-input-wrap">
574 <?php
575
576 acf_render_field(
577 array(
578 'type' => 'select',
579 'name' => 'display',
580 'prefix' => $layout_prefix,
581 'value' => $layout['display'],
582 'class' => 'acf-is-prepended',
583 'choices' => array(
584 'table' => __( 'Table', 'acf' ),
585 'block' => __( 'Block', 'acf' ),
586 'row' => __( 'Row', 'acf' ),
587 ),
588 )
589 );
590
591 ?>
592 </div>
593 </li>
594 <li class="acf-fc-meta-min">
595 <?php
596
597 acf_render_field(
598 array(
599 'type' => 'text',
600 'name' => 'min',
601 'prefix' => $layout_prefix,
602 'value' => $layout['min'],
603 'prepend' => __( 'Min', 'acf' ),
604 )
605 );
606
607 ?>
608 </li>
609 <li class="acf-fc-meta-max">
610 <?php
611
612 acf_render_field(
613 array(
614 'type' => 'text',
615 'name' => 'max',
616 'prefix' => $layout_prefix,
617 'value' => $layout['max'],
618 'prepend' => __( 'Max', 'acf' ),
619 )
620 );
621
622 ?>
623 </li>
624 </ul>
625 <div class="acf-input-sub">
626 <?php
627
628 // vars
629 $args = array(
630 'fields' => $layout['sub_fields'],
631 'parent' => $field['ID'],
632 'is_subfield' => true,
633 );
634
635 acf_get_view( 'acf-field-group/fields', $args );
636
637 ?>
638 </div>
639 </div>
640 </div>
641 <?php
642 }
643 }
644
645 /**
646 * Renders the field settings used in the "Presentation" tab.
647 *
648 * @since 6.0
649 *
650 * @param array $field The field settings array.
651 * @return void
652 */
653 function render_field_presentation_settings( $field ) {
654
655 // min
656 acf_render_field_setting(
657 $field,
658 array(
659 'label' => __( 'Minimum Layouts', 'acf' ),
660 'instructions' => '',
661 'type' => 'number',
662 'name' => 'min',
663 )
664 );
665
666 // max
667 acf_render_field_setting(
668 $field,
669 array(
670 'label' => __( 'Maximum Layouts', 'acf' ),
671 'instructions' => '',
672 'type' => 'number',
673 'name' => 'max',
674 )
675 );
676
677 // add new row label
678 acf_render_field_setting(
679 $field,
680 array(
681 'label' => __( 'Button Label', 'acf' ),
682 'instructions' => '',
683 'type' => 'text',
684 'name' => 'button_label',
685 )
686 );
687 }
688
689
690 /**
691 * This filter is applied to the $value after it is loaded from the db
692 *
693 * @type filter
694 * @since 3.6
695 *
696 * @param mixed $value The value found in the database
697 * @param mixed $post_id The post_id from which the value was loaded
698 * @param array $field The field array holding all the field options
699 * @return $value
700 */
701 public function load_value( $value, $post_id, $field ) {
702 // bail early if no value
703 if ( empty( $value ) || empty( $field['layouts'] ) ) {
704 return $value;
705 }
706
707 // value must be an array
708 $value = acf_get_array( $value );
709
710 // vars
711 $rows = array();
712
713 // sort layouts into names
714 $layouts = array();
715 foreach ( $field['layouts'] as $k => $layout ) {
716 $layouts[ $layout['name'] ] = $layout['sub_fields'];
717 }
718
719 // loop through rows
720 foreach ( $value as $i => $l ) {
721
722 // append to $values
723 $rows[ $i ] = array();
724 $rows[ $i ]['acf_fc_layout'] = $l;
725
726 // bail early if layout deosnt contain sub fields
727 if ( empty( $layouts[ $l ] ) ) {
728 continue;
729 }
730
731 // get layout
732 $layout = $layouts[ $l ];
733
734 // loop through sub fields
735 foreach ( array_keys( $layout ) as $j ) {
736
737 // get sub field
738 $sub_field = $layout[ $j ];
739
740 // bail early if no name (tab)
741 if ( acf_is_empty( $sub_field['name'] ) ) {
742 continue;
743 }
744
745 // update full name
746 $sub_field['name'] = "{$field['name']}_{$i}_{$sub_field['name']}";
747
748 // get value
749 $sub_value = acf_get_value( $post_id, $sub_field );
750
751 // add value
752 $rows[ $i ][ $sub_field['key'] ] = $sub_value;
753 }
754 }
755
756 return $rows;
757 }
758
759
760 /**
761 * This filter is appied to the $value after it is loaded from the db and before it is returned to the template
762 *
763 * @type filter
764 * @since 3.6
765 *
766 * @param mixed $value The value which was loaded from the database.
767 * @param mixed $post_id The $post_id from which the value was loaded.
768 * @param array $field The field array holding all the field options.
769 * @param boolean $escape_html Should the field return a HTML safe formatted value.
770 * @return mixed $value The modified value.
771 */
772 public function format_value( $value, $post_id, $field, $escape_html = false ) {
773
774 // bail early if no value
775 if ( empty( $value ) || empty( $field['layouts'] ) ) {
776 return false;
777 }
778
779 // sort layouts into names
780 $layouts = array();
781 foreach ( $field['layouts'] as $k => $layout ) {
782 $layouts[ $layout['name'] ] = $layout['sub_fields'];
783 }
784
785 // loop over rows
786 foreach ( array_keys( $value ) as $i ) {
787
788 // get layout name
789 $l = $value[ $i ]['acf_fc_layout'];
790
791 // bail early if layout deosnt exist
792 if ( empty( $layouts[ $l ] ) ) {
793 continue;
794 }
795
796 // get layout
797 $layout = $layouts[ $l ];
798
799 // loop through sub fields
800 foreach ( array_keys( $layout ) as $j ) {
801
802 // get sub field
803 $sub_field = $layout[ $j ];
804
805 // bail early if no name (tab)
806 if ( acf_is_empty( $sub_field['name'] ) ) {
807 continue;
808 }
809
810 // extract value
811 $sub_value = acf_extract_var( $value[ $i ], $sub_field['key'] );
812
813 // update $sub_field name
814 $sub_field['name'] = "{$field['name']}_{$i}_{$sub_field['name']}";
815
816 // format value
817 $sub_value = acf_format_value( $sub_value, $post_id, $sub_field, $escape_html );
818
819 // append to $row
820 $value[ $i ][ $sub_field['_name'] ] = $sub_value;
821 }
822 }
823
824 // return
825 return $value;
826 }
827
828
829 /**
830 * description
831 *
832 * @type function
833 * @date 11/02/2014
834 * @since 5.0.0
835 *
836 * @param $post_id (int)
837 * @return $post_id (int)
838 */
839 public function validate_value( $valid, $value, $field, $input ) {
840
841 // vars
842 $count = 0;
843
844 // check if is value (may be empty string)
845 if ( is_array( $value ) ) {
846
847 // remove acfcloneindex
848 if ( isset( $value['acfcloneindex'] ) ) {
849 unset( $value['acfcloneindex'] );
850 }
851
852 // count
853 $count = count( $value );
854 }
855
856 // validate required
857 if ( $field['required'] && ! $count ) {
858 $valid = false;
859 }
860
861 // validate min
862 $min = (int) $field['min'];
863 if ( $min && $count < $min ) {
864
865 // vars
866 $error = __( 'This field requires at least {min} {label} {identifier}', 'acf' );
867 $identifier = _n( 'layout', 'layouts', $min, 'acf' );
868
869 // replace
870 $error = str_replace( '{min}', $min, $error );
871 $error = str_replace( '{label}', '', $error );
872 $error = str_replace( '{identifier}', $identifier, $error );
873
874 // return
875 return $error;
876 }
877
878 // find layouts
879 $layouts = array();
880 foreach ( array_keys( $field['layouts'] ) as $i ) {
881
882 // vars
883 $layout = $field['layouts'][ $i ];
884
885 // add count
886 $layout['count'] = 0;
887
888 // append
889 $layouts[ $layout['name'] ] = $layout;
890 }
891
892 // validate value
893 if ( $count ) {
894
895 // loop rows
896 foreach ( $value as $i => $row ) {
897 // ensure row is an array
898 if ( ! is_array( $row ) ) {
899 continue;
900 }
901
902 // get layout
903 $l = $row['acf_fc_layout'];
904
905 // bail if layout doesn't exist
906 if ( ! isset( $layouts[ $l ] ) ) {
907 continue;
908 }
909
910 // increase count
911 ++$layouts[ $l ]['count'];
912
913 // bail if no sub fields
914 if ( empty( $layouts[ $l ]['sub_fields'] ) ) {
915 continue;
916 }
917
918 // loop sub fields
919 foreach ( $layouts[ $l ]['sub_fields'] as $sub_field ) {
920
921 // get sub field key
922 $k = $sub_field['key'];
923
924 // bail if no value
925 if ( ! isset( $value[ $i ][ $k ] ) ) {
926 continue;
927 }
928
929 // validate
930 acf_validate_value( $value[ $i ][ $k ], $sub_field, "{$input}[{$i}][{$k}]" );
931 }
932 // end loop sub fields
933 }
934 // end loop rows
935 }
936
937 // validate layouts
938 foreach ( $layouts as $layout ) {
939
940 // validate min / max
941 $min = (int) $layout['min'];
942 $count = $layout['count'];
943 $label = $layout['label'];
944
945 if ( $min && $count < $min ) {
946
947 // vars
948 $error = __( 'This field requires at least {min} {label} {identifier}', 'acf' );
949 $identifier = _n( 'layout', 'layouts', $min, 'acf' );
950
951 // replace
952 $error = str_replace( '{min}', $min, $error );
953 $error = str_replace( '{label}', '"' . $label . '"', $error );
954 $error = str_replace( '{identifier}', $identifier, $error );
955
956 // return
957 return $error;
958 }
959 }
960
961 // return
962 return $valid;
963 }
964
965
966 /**
967 * This function will return a specific layout by name from a field
968 *
969 * @since 5.5.8
970 *
971 * @param string $name The layout name.
972 * @param array $field The field to load the layout from.
973 * @return array|false
974 */
975 public function get_layout( $name, $field ) {
976
977 // bail early if no layouts
978 if ( ! isset( $field['layouts'] ) ) {
979 return false;
980 }
981
982 // loop
983 foreach ( $field['layouts'] as $layout ) {
984
985 // match
986 if ( $layout['name'] === $name ) {
987 return $layout;
988 }
989 }
990
991 // return
992 return false;
993 }
994
995
996 /**
997 * This function will delete a value row
998 *
999 * @date 15/2/17
1000 * @since 5.5.8
1001 *
1002 * @param integer $i
1003 * @param array $field
1004 * @param mixed $post_id
1005 * @return boolean
1006 */
1007 public function delete_row( $i, $field, $post_id ) {
1008
1009 // vars
1010 $value = acf_get_metadata( $post_id, $field['name'] );
1011
1012 // bail early if no value
1013 if ( ! is_array( $value ) || ! isset( $value[ $i ] ) ) {
1014 return false;
1015 }
1016
1017 // get layout
1018 $layout = $this->get_layout( $value[ $i ], $field );
1019
1020 // bail early if no layout
1021 if ( ! $layout || empty( $layout['sub_fields'] ) ) {
1022 return false;
1023 }
1024
1025 // loop
1026 foreach ( $layout['sub_fields'] as $sub_field ) {
1027
1028 // modify name for delete
1029 $sub_field['name'] = "{$field['name']}_{$i}_{$sub_field['name']}";
1030
1031 // delete value
1032 acf_delete_value( $post_id, $sub_field );
1033 }
1034
1035 // return
1036 return true;
1037 }
1038
1039 /**
1040 * This function will update a value row
1041 *
1042 * @date 15/2/17
1043 * @since 5.5.8
1044 *
1045 * @param array $row
1046 * @param integer $i
1047 * @param array $field
1048 * @param mixed $post_id
1049 * @return boolean
1050 */
1051 public function update_row( $row, $i, $field, $post_id ) {
1052 // bail early if no layout reference
1053 if ( ! is_array( $row ) || ! isset( $row['acf_fc_layout'] ) ) {
1054 return false;
1055 }
1056
1057 // get layout
1058 $layout = $this->get_layout( $row['acf_fc_layout'], $field );
1059
1060 // bail early if no layout
1061 if ( ! $layout || empty( $layout['sub_fields'] ) ) {
1062 return false;
1063 }
1064
1065 foreach ( $layout['sub_fields'] as $sub_field ) {
1066 $value = null;
1067
1068 if ( array_key_exists( $sub_field['key'], $row ) ) {
1069 $value = $row[ $sub_field['key'] ];
1070 } elseif ( array_key_exists( $sub_field['name'], $row ) ) {
1071 $value = $row[ $sub_field['name'] ];
1072 } else {
1073 // Value does not exist.
1074 continue;
1075 }
1076
1077 // modify name for save
1078 $sub_field['name'] = "{$field['name']}_{$i}_{$sub_field['name']}";
1079
1080 // update field
1081 acf_update_value( $value, $post_id, $sub_field );
1082 }
1083
1084 return true;
1085 }
1086
1087 /**
1088 * This filter is appied to the $value before it is updated in the db
1089 *
1090 * @type filter
1091 * @since 3.6
1092 *
1093 * @param mixed $value The value which will be saved in the database
1094 * @param mixed $post_id The post_id of which the value will be saved
1095 * @param array $field The field array holding all the field options
1096 * @return mixed $value The modified value
1097 */
1098 public function update_value( $value, $post_id, $field ) {
1099
1100 // bail early if no layouts
1101 if ( empty( $field['layouts'] ) ) {
1102 return $value;
1103 }
1104
1105 // vars
1106 $new_value = array();
1107 $old_value = acf_get_metadata( $post_id, $field['name'] );
1108 $old_value = is_array( $old_value ) ? $old_value : array();
1109
1110 // update
1111 if ( ! empty( $value ) ) {
1112 $i = -1;
1113
1114 // remove acfcloneindex
1115 if ( isset( $value['acfcloneindex'] ) ) {
1116 unset( $value['acfcloneindex'] );
1117 }
1118
1119 // loop through rows
1120 foreach ( $value as $row ) {
1121 ++$i;
1122
1123 // bail early if no layout reference
1124 if ( ! is_array( $row ) || ! isset( $row['acf_fc_layout'] ) ) {
1125 continue;
1126 }
1127
1128 // delete old row if layout has changed
1129 if ( isset( $old_value[ $i ] ) && $old_value[ $i ] !== $row['acf_fc_layout'] ) {
1130 $this->delete_row( $i, $field, $post_id );
1131 }
1132
1133 // update row
1134 $this->update_row( $row, $i, $field, $post_id );
1135
1136 // append to order
1137 $new_value[] = $row['acf_fc_layout'];
1138 }
1139 }
1140
1141 // vars
1142 $old_count = empty( $old_value ) ? 0 : count( $old_value );
1143 $new_count = empty( $new_value ) ? 0 : count( $new_value );
1144
1145 // remove old rows
1146 if ( $old_count > $new_count ) {
1147
1148 // loop
1149 for ( $i = $new_count; $i < $old_count; $i++ ) {
1150 $this->delete_row( $i, $field, $post_id );
1151 }
1152 }
1153
1154 // save false for empty value
1155 if ( empty( $new_value ) ) {
1156 $new_value = '';
1157 }
1158
1159 // return
1160 return $new_value;
1161 }
1162
1163
1164 /**
1165 * Deletes a layout from a flexible content field.
1166 *
1167 * @type function
1168 * @date 1/07/2015
1169 * @since 5.2.3
1170 *
1171 * @param $post_id (int)
1172 * @return $post_id (int)
1173 */
1174 public function delete_value( $post_id, $key, $field ) {
1175
1176 // vars
1177 $old_value = acf_get_metadata( $post_id, $field['name'] );
1178 $old_value = is_array( $old_value ) ? $old_value : array();
1179
1180 // bail early if no rows or no sub fields
1181 if ( empty( $old_value ) ) {
1182 return;
1183 }
1184
1185 // loop
1186 foreach ( array_keys( $old_value ) as $i ) {
1187 $this->delete_row( $i, $field, $post_id );
1188 }
1189 }
1190
1191
1192 /**
1193 * This filter is appied to the $field before it is saved to the database
1194 *
1195 * @type filter
1196 * @since 3.6
1197 *
1198 * @param array $field The field array holding all the field options
1199 * @return array $field The modified field
1200 */
1201 public function update_field( $field ) {
1202
1203 // loop
1204 if ( ! empty( $field['layouts'] ) ) {
1205 foreach ( $field['layouts'] as &$layout ) {
1206 unset( $layout['sub_fields'] );
1207 }
1208 }
1209
1210 // return
1211 return $field;
1212 }
1213
1214
1215 /**
1216 * description
1217 *
1218 * @type function
1219 * @date 4/04/2014
1220 * @since 5.0.0
1221 *
1222 * @param $post_id (int)
1223 * @return $post_id (int)
1224 */
1225 function delete_field( $field ) {
1226
1227 if ( ! empty( $field['layouts'] ) ) {
1228
1229 // loop through layouts
1230 foreach ( $field['layouts'] as $layout ) {
1231
1232 // loop through sub fields
1233 if ( ! empty( $layout['sub_fields'] ) ) {
1234 foreach ( $layout['sub_fields'] as $sub_field ) {
1235 acf_delete_field( $sub_field['ID'] );
1236 }
1237 // foreach
1238 }
1239 // if
1240 }
1241 // foreach
1242 }
1243 // if
1244 }
1245
1246
1247 /**
1248 * This filter is appied to the $field before it is duplicated and saved to the database
1249 *
1250 * @type filter
1251 * @since 3.6
1252 * @date 23/01/13
1253 *
1254 * @param $field - the field array holding all the field options
1255 *
1256 * @return $field - the modified field
1257 */
1258 function duplicate_field( $field ) {
1259
1260 // vars
1261 $sub_fields = array();
1262
1263 if ( ! empty( $field['layouts'] ) ) {
1264
1265 // loop through layouts
1266 foreach ( $field['layouts'] as $layout ) {
1267
1268 // extract sub fields
1269 $extra = acf_extract_var( $layout, 'sub_fields' );
1270
1271 // merge
1272 if ( ! empty( $extra ) ) {
1273 $sub_fields = array_merge( $sub_fields, $extra );
1274 }
1275 }
1276 // foreach
1277 }
1278
1279 // save field to get ID
1280 $field = acf_update_field( $field );
1281
1282 // duplicate sub fields
1283 acf_duplicate_fields( $sub_fields, $field['ID'] );
1284
1285 return $field;
1286 }
1287
1288
1289 /**
1290 * Output the layout title for an AJAX response.
1291 *
1292 * @since 5.3.2
1293 */
1294 public function ajax_layout_title() {
1295
1296 $options = acf_parse_args(
1297 $_POST, // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Verified elsewhere.
1298 array(
1299 'post_id' => 0,
1300 'i' => 0,
1301 'field_key' => '',
1302 'nonce' => '',
1303 'layout' => '',
1304 'value' => array(),
1305 )
1306 );
1307
1308 // load field
1309 $field = acf_get_field( $options['field_key'] );
1310 if ( ! $field ) {
1311 die();
1312 }
1313
1314 // vars
1315 $layout = $this->get_layout( $options['layout'], $field );
1316 if ( ! $layout ) {
1317 die();
1318 }
1319
1320 // title
1321 $title = $this->get_layout_title( $field, $layout, $options['i'], $options['value'] );
1322
1323 // echo
1324 echo acf_esc_html( $title );
1325 die;
1326 }
1327
1328
1329 /**
1330 * Get a layout title for a field.
1331 *
1332 * @param array $field The field array
1333 * @param array $layout The layout array
1334 * @param integer $i The order number of the layout
1335 * @param array $value The value of the layout
1336 * @return string The layout title, optionally filtered.
1337 */
1338 public function get_layout_title( $field, $layout, $i, $value ) {
1339
1340 // vars
1341 $rows = array();
1342 $rows[ $i ] = $value;
1343
1344 // add loop
1345 acf_add_loop(
1346 array(
1347 'selector' => $field['name'],
1348 'name' => $field['name'],
1349 'value' => $rows,
1350 'field' => $field,
1351 'i' => $i,
1352 'post_id' => 0,
1353 )
1354 );
1355
1356 // vars
1357 $title = $layout['label'];
1358
1359 // filters
1360 $title = apply_filters( 'acf/fields/flexible_content/layout_title', $title, $field, $layout, $i );
1361 $title = apply_filters( 'acf/fields/flexible_content/layout_title/name=' . $field['_name'], $title, $field, $layout, $i );
1362 $title = apply_filters( 'acf/fields/flexible_content/layout_title/key=' . $field['key'], $title, $field, $layout, $i );
1363
1364 // remove loop
1365 acf_remove_loop();
1366
1367 // prepend order
1368 $order = is_numeric( $i ) ? $i + 1 : 0;
1369 $title = '<span class="acf-fc-layout-order">' . $order . '</span> ' . acf_esc_html( $title );
1370
1371 return $title;
1372 }
1373
1374
1375 /**
1376 * This function will update clone field settings based on the origional field
1377 *
1378 * @type function
1379 * @date 28/06/2016
1380 * @since 5.3.8
1381 *
1382 * @param $clone (array)
1383 * @param $field (array)
1384 * @return $clone
1385 */
1386 public function clone_any_field( $field, $clone_field ) {
1387
1388 // remove parent_layout
1389 // - allows a sub field to be rendered as a normal field
1390 unset( $field['parent_layout'] );
1391
1392 // attempt to merger parent_layout
1393 if ( isset( $clone_field['parent_layout'] ) ) {
1394 $field['parent_layout'] = $clone_field['parent_layout'];
1395 }
1396
1397 // return
1398 return $field;
1399 }
1400
1401
1402 /**
1403 * Handles preparing the layouts for export.
1404 *
1405 * @since 5.0.0
1406 *
1407 * @param array $field The whole fiel array
1408 * @return array The export ready field array.
1409 */
1410 public function prepare_field_for_export( $field ) {
1411
1412 // loop
1413 if ( ! empty( $field['layouts'] ) ) {
1414 foreach ( $field['layouts'] as &$layout ) {
1415 $layout['sub_fields'] = acf_prepare_fields_for_export( $layout['sub_fields'] );
1416 }
1417 }
1418
1419 // return
1420 return $field;
1421 }
1422
1423 function prepare_any_field_for_export( $field ) {
1424
1425 // remove parent_layout
1426 unset( $field['parent_layout'] );
1427
1428 // return
1429 return $field;
1430 }
1431
1432
1433 /**
1434 * description
1435 *
1436 * @type function
1437 * @date 11/03/2014
1438 * @since 5.0.0
1439 *
1440 * @param $post_id (int)
1441 * @return $post_id (int)
1442 */
1443 public function prepare_field_for_import( $field ) {
1444
1445 // Bail early if no layouts
1446 if ( empty( $field['layouts'] ) ) {
1447 return $field;
1448 }
1449
1450 // Storage for extracted fields.
1451 $extra = array();
1452
1453 // Loop over layouts.
1454 foreach ( $field['layouts'] as &$layout ) {
1455
1456 // Ensure layout is valid.
1457 $layout = $this->get_valid_layout( $layout );
1458
1459 // Extract sub fields.
1460 $sub_fields = acf_extract_var( $layout, 'sub_fields' );
1461
1462 // Modify and append sub fields to $extra.
1463 if ( $sub_fields ) {
1464 foreach ( $sub_fields as $i => $sub_field ) {
1465
1466 // Update atttibutes
1467 $sub_field['parent'] = $field['key'];
1468 $sub_field['parent_layout'] = $layout['key'];
1469 $sub_field['menu_order'] = $i;
1470
1471 // Append to extra.
1472 $extra[] = $sub_field;
1473 }
1474 }
1475 }
1476
1477 // Merge extra sub fields.
1478 if ( $extra ) {
1479 array_unshift( $extra, $field );
1480 return $extra;
1481 }
1482
1483 // Return field.
1484 return $field;
1485 }
1486
1487
1488 /**
1489 * This function will add compatibility for the 'column_width' setting
1490 *
1491 * @type function
1492 * @date 30/1/17
1493 * @since 5.5.6
1494 *
1495 * @param $field (array)
1496 * @return $field
1497 */
1498 function validate_any_field( $field ) {
1499
1500 // width has changed
1501 if ( isset( $field['column_width'] ) ) {
1502 $field['wrapper']['width'] = acf_extract_var( $field, 'column_width' );
1503 }
1504
1505 // return
1506 return $field;
1507 }
1508
1509
1510 /**
1511 * This function will translate field settings
1512 *
1513 * @type function
1514 * @date 8/03/2016
1515 * @since 5.3.2
1516 *
1517 * @param $field (array)
1518 * @return $field
1519 */
1520 function translate_field( $field ) {
1521
1522 // translate
1523 $field['button_label'] = acf_translate( $field['button_label'] );
1524
1525 // loop
1526 if ( ! empty( $field['layouts'] ) ) {
1527 foreach ( $field['layouts'] as &$layout ) {
1528 $layout['label'] = acf_translate( $layout['label'] );
1529 }
1530 }
1531
1532 // return
1533 return $field;
1534 }
1535
1536 /**
1537 * Additional validation for the flexible content field when submitted via REST.
1538 *
1539 * @param boolean $valid The current validity booleean
1540 * @param integer $value The value of the field
1541 * @param array $field The field array
1542 * @return boolean|WP
1543 */
1544 public function validate_rest_value( $valid, $value, $field ) {
1545 $param = sprintf( '%s[%s]', $field['prefix'], $field['name'] );
1546 $data = array(
1547 'param' => $param,
1548 'value' => $value,
1549 );
1550
1551 if ( ! is_array( $value ) && is_null( $value ) ) {
1552 $error = sprintf( __( '%s must be of type array or null.', 'acf' ), $param );
1553 return new WP_Error( 'rest_invalid_param', $error, $param );
1554 }
1555
1556 $layouts_to_update = array_count_values( array_column( $value, 'acf_fc_layout' ) );
1557
1558 foreach ( $field['layouts'] as $layout ) {
1559 $num_layouts = isset( $layouts_to_update[ $layout['name'] ] ) ? $layouts_to_update[ $layout['name'] ] : 0;
1560
1561 if ( '' !== $layout['min'] && $num_layouts < (int) $layout['min'] ) {
1562 $error = sprintf(
1563 _n(
1564 '%1$s must contain at least %2$s %3$s layout.',
1565 '%1$s must contain at least %2$s %3$s layouts.',
1566 $layout['min'],
1567 'acf'
1568 ),
1569 $param,
1570 number_format_i18n( $layout['min'] ),
1571 $layout['name']
1572 );
1573
1574 return new WP_Error( 'rest_invalid_param', $error, $data );
1575 }
1576
1577 if ( '' !== $layout['max'] && $num_layouts > (int) $layout['max'] ) {
1578 $error = sprintf(
1579 _n(
1580 '%1$s must contain at most %2$s %3$s layout.',
1581 '%1$s must contain at most %2$s %3$s layouts.',
1582 $layout['max'],
1583 'acf'
1584 ),
1585 $param,
1586 number_format_i18n( $layout['max'] ),
1587 $layout['name']
1588 );
1589
1590 return new WP_Error( 'rest_invalid_param', $error, $data );
1591 }
1592 }
1593
1594 return $valid;
1595 }
1596
1597 /**
1598 * Return the schema array for the REST API.
1599 *
1600 * @param array $field
1601 * @return array
1602 */
1603 public function get_rest_schema( array $field ) {
1604 $schema = array(
1605 'type' => array( 'array', 'null' ),
1606 'required' => ! empty( $field['required'] ),
1607 'items' => array(
1608 'oneOf' => array(),
1609 ),
1610 );
1611
1612 // Loop through layouts building up a schema for each.
1613 foreach ( $field['layouts'] as $layout ) {
1614 $layout_schema = array(
1615 'type' => 'object',
1616 'properties' => array(
1617 'acf_fc_layout' => array(
1618 'type' => 'string',
1619 'required' => true,
1620 // By using a pattern match against the layout name, data sent in must match an available
1621 // layout on the flexible field. If it doesn't, a 400 Bad Request response will result.
1622 'pattern' => '^' . $layout['name'] . '$',
1623 ),
1624 ),
1625 );
1626
1627 foreach ( $layout['sub_fields'] as $sub_field ) {
1628 if ( $sub_field_schema = acf_get_field_rest_schema( $sub_field ) ) {
1629 $layout_schema['properties'][ $sub_field['name'] ] = $sub_field_schema;
1630 }
1631 }
1632
1633 $schema['items']['oneOf'][] = $layout_schema;
1634 }
1635
1636 if ( ! empty( $field['min'] ) ) {
1637 $schema['minItems'] = (int) $field['min'];
1638 }
1639
1640 if ( ! empty( $field['max'] ) ) {
1641 $schema['maxItems'] = (int) $field['max'];
1642 }
1643
1644 return $schema;
1645 }
1646
1647 /**
1648 * Apply basic formatting to prepare the value for default REST output.
1649 *
1650 * @param mixed $value
1651 * @param integer|string $post_id
1652 * @param array $field
1653 * @return array|mixed
1654 */
1655 public function format_value_for_rest( $value, $post_id, array $field ) {
1656 if ( empty( $value ) || ! is_array( $value ) || empty( $field['layouts'] ) ) {
1657 return null;
1658 }
1659
1660 // Create a map of layout sub fields mapped to layout names.
1661 foreach ( $field['layouts'] as $layout ) {
1662 $layouts[ $layout['name'] ] = $layout['sub_fields'];
1663 }
1664
1665 // Loop through each layout and within that, each sub field to process sub fields individually.
1666 foreach ( $value as &$layout ) {
1667 $name = $layout['acf_fc_layout'];
1668
1669 if ( empty( $layouts[ $name ] ) ) {
1670 continue;
1671 }
1672
1673 foreach ( $layouts[ $name ] as $sub_field ) {
1674
1675 // Bail early if the field has no name (tab).
1676 if ( acf_is_empty( $sub_field['name'] ) ) {
1677 continue;
1678 }
1679
1680 // Extract the sub field 'field_key'=>'value' pair from the $layout and format it.
1681 $sub_value = acf_extract_var( $layout, $sub_field['key'] );
1682 $sub_value = acf_format_value_for_rest( $sub_value, $post_id, $sub_field );
1683
1684 // Add the sub field value back to the $layout but mapped to the field name instead
1685 // of the key reference.
1686 $layout[ $sub_field['name'] ] = $sub_value;
1687 }
1688 }
1689
1690 return $value;
1691 }
1692 }
1693
1694
1695 // initialize
1696 acf_register_field_type( 'acf_field_flexible_content' );
1697 endif; // class_exists check
1698
1699 ?>
1700