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