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