123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851 |
- <?php
- if ( ! class_exists( 'acf_field_flexible_content' ) ) :
- class acf_field_flexible_content extends acf_field {
- /*
- * __construct
- *
- * This function will setup the field type data
- *
- * @type function
- * @date 5/03/2014
- * @since 5.0.0
- *
- * @param n/a
- * @return n/a
- */
- function initialize() {
- // vars
- $this->name = 'flexible_content';
- $this->label = __( 'Flexible Content', 'acf' );
- $this->category = 'layout';
- $this->defaults = array(
- 'layouts' => array(),
- 'min' => '',
- 'max' => '',
- 'button_label' => __( 'Add Row', 'acf' ),
- );
- // ajax
- $this->add_action( 'wp_ajax_acf/fields/flexible_content/layout_title', array( $this, 'ajax_layout_title' ) );
- $this->add_action( 'wp_ajax_nopriv_acf/fields/flexible_content/layout_title', array( $this, 'ajax_layout_title' ) );
- // filters
- $this->add_filter( 'acf/prepare_field_for_export', array( $this, 'prepare_any_field_for_export' ) );
- $this->add_filter( 'acf/clone_field', array( $this, 'clone_any_field' ), 10, 2 );
- $this->add_filter( 'acf/validate_field', array( $this, 'validate_any_field' ) );
- // field filters
- $this->add_field_filter( 'acf/get_sub_field', array( $this, 'get_sub_field' ), 10, 3 );
- $this->add_field_filter( 'acf/prepare_field_for_export', array( $this, 'prepare_field_for_export' ) );
- $this->add_field_filter( 'acf/prepare_field_for_import', array( $this, 'prepare_field_for_import' ) );
- }
- /*
- * input_admin_enqueue_scripts
- *
- * description
- *
- * @type function
- * @date 16/12/2015
- * @since 5.3.2
- *
- * @param $post_id (int)
- * @return $post_id (int)
- */
- function input_admin_enqueue_scripts() {
- // localize
- acf_localize_text(
- array(
- // identifiers
- 'layout' => __( 'layout', 'acf' ),
- 'layouts' => __( 'layouts', 'acf' ),
- 'Fields' => __( 'Fields', 'acf' ),
- // min / max
- 'This field requires at least {min} {label} {identifier}' => __( 'This field requires at least {min} {label} {identifier}', 'acf' ),
- 'This field has a limit of {max} {label} {identifier}' => __( 'This field has a limit of {max} {label} {identifier}', 'acf' ),
- // popup badge
- '{available} {label} {identifier} available (max {max})' => __( '{available} {label} {identifier} available (max {max})', 'acf' ),
- '{required} {label} {identifier} required (min {min})' => __( '{required} {label} {identifier} required (min {min})', 'acf' ),
- // field settings
- 'Flexible Content requires at least 1 layout' => __( 'Flexible Content requires at least 1 layout', 'acf' ),
- )
- );
- }
- /*
- * get_valid_layout
- *
- * This function will fill in the missing keys to create a valid layout
- *
- * @type function
- * @date 3/10/13
- * @since 1.1.0
- *
- * @param $layout (array)
- * @return $layout (array)
- */
- function get_valid_layout( $layout = array() ) {
- // parse
- $layout = wp_parse_args(
- $layout,
- array(
- 'key' => uniqid( 'layout_' ),
- 'name' => '',
- 'label' => '',
- 'display' => 'block',
- 'sub_fields' => array(),
- 'min' => '',
- 'max' => '',
- )
- );
- // return
- return $layout;
- }
- /*
- * load_field()
- *
- * This filter is appied to the $field after it is loaded from the database
- *
- * @type filter
- * @since 3.6
- * @date 23/01/13
- *
- * @param $field - the field array holding all the field options
- *
- * @return $field - the field array holding all the field options
- */
- function load_field( $field ) {
- // bail early if no field layouts
- if ( empty( $field['layouts'] ) ) {
- return $field;
- }
- // vars
- $sub_fields = acf_get_fields( $field );
- // loop through layouts, sub fields and swap out the field key with the real field
- foreach ( array_keys( $field['layouts'] ) as $i ) {
- // extract layout
- $layout = acf_extract_var( $field['layouts'], $i );
- // validate layout
- $layout = $this->get_valid_layout( $layout );
- // append sub fields
- if ( ! empty( $sub_fields ) ) {
- foreach ( array_keys( $sub_fields ) as $k ) {
- // check if 'parent_layout' is empty
- if ( empty( $sub_fields[ $k ]['parent_layout'] ) ) {
- // parent_layout did not save for this field, default it to first layout
- $sub_fields[ $k ]['parent_layout'] = $layout['key'];
- }
- // append sub field to layout,
- if ( $sub_fields[ $k ]['parent_layout'] == $layout['key'] ) {
- $layout['sub_fields'][] = acf_extract_var( $sub_fields, $k );
- }
- }
- }
- // append back to layouts
- $field['layouts'][ $i ] = $layout;
- }
- // return
- return $field;
- }
- /*
- * get_sub_field
- *
- * This function will return a specific sub field
- *
- * @type function
- * @date 29/09/2016
- * @since 5.4.0
- *
- * @param $sub_field
- * @param $selector (string)
- * @param $field (array)
- * @return $post_id (int)
- */
- function get_sub_field( $sub_field, $id, $field ) {
- // Get active layout.
- $active = get_row_layout();
- // Loop over layouts.
- if ( $field['layouts'] ) {
- foreach ( $field['layouts'] as $layout ) {
- // Restict to active layout if within a have_rows() loop.
- if ( $active && $active !== $layout['name'] ) {
- continue;
- }
- // Check sub fields.
- if ( $layout['sub_fields'] ) {
- $sub_field = acf_search_fields( $id, $layout['sub_fields'] );
- if ( $sub_field ) {
- break;
- }
- }
- }
- }
- // return
- return $sub_field;
- }
- /*
- * render_field()
- *
- * Create the HTML interface for your field
- *
- * @param $field - an array holding all the field's data
- *
- * @type action
- * @since 3.6
- * @date 23/01/13
- */
- function render_field( $field ) {
- // defaults
- if ( empty( $field['button_label'] ) ) {
- $field['button_label'] = $this->defaults['button_label'];
- }
- // sort layouts into names
- $layouts = array();
- foreach ( $field['layouts'] as $k => $layout ) {
- $layouts[ $layout['name'] ] = $layout;
- }
- // vars
- $div = array(
- 'class' => 'acf-flexible-content',
- 'data-min' => $field['min'],
- 'data-max' => $field['max'],
- );
- // empty
- if ( empty( $field['value'] ) ) {
- $div['class'] .= ' -empty';
- }
- // no value message
- $no_value_message = __( 'Click the "%s" button below to start creating your layout', 'acf' );
- $no_value_message = apply_filters( 'acf/fields/flexible_content/no_value_message', $no_value_message, $field );
- $no_value_message = sprintf( $no_value_message, $field['button_label'] );
- ?>
- <div <?php echo acf_esc_attrs( $div ); ?>>
- <?php acf_hidden_input( array( 'name' => $field['name'] ) ); ?>
- <div class="no-value-message">
- <?php echo acf_esc_html( $no_value_message ); ?>
- </div>
- <div class="clones">
- <?php foreach ( $layouts as $layout ) : ?>
- <?php $this->render_layout( $field, $layout, 'acfcloneindex', array() ); ?>
- <?php endforeach; ?>
- </div>
- <div class="values">
- <?php
- if ( ! empty( $field['value'] ) ) :
- foreach ( $field['value'] as $i => $value ) :
- // validate
- if ( empty( $layouts[ $value['acf_fc_layout'] ] ) ) {
- continue;
- }
- // render
- $this->render_layout( $field, $layouts[ $value['acf_fc_layout'] ], $i, $value );
- endforeach;
- endif;
- ?>
- </div>
- <div class="acf-actions">
- <a class="acf-button button button-primary" href="#" data-name="add-layout"><?php echo acf_esc_html( $field['button_label'] ); ?></a>
- </div>
- <script type="text-html" class="tmpl-popup"><ul>
- <?php
- foreach ( $layouts as $layout ) :
- $atts = array(
- 'href' => '#',
- 'data-layout' => $layout['name'],
- 'data-min' => $layout['min'],
- 'data-max' => $layout['max'],
- );
- ?>
- <li><a <?php echo acf_esc_attrs( $atts ); ?>><?php echo acf_esc_html( $layout['label'] ); ?></a></li>
- <?php
- endforeach;
- ?>
- </ul>
- </script>
- </div>
- <?php
- }
- /*
- * render_layout
- *
- * description
- *
- * @type function
- * @date 19/11/2013
- * @since 5.0.0
- *
- * @param $post_id (int)
- * @return $post_id (int)
- */
- function render_layout( $field, $layout, $i, $value ) {
- // vars
- $order = 0;
- $el = 'div';
- $sub_fields = $layout['sub_fields'];
- $id = ( $i === 'acfcloneindex' ) ? 'acfcloneindex' : "row-$i";
- $prefix = $field['name'] . '[' . $id . ']';
- // div
- $div = array(
- 'class' => 'layout',
- 'data-id' => $id,
- 'data-layout' => $layout['name'],
- );
- // clone
- if ( is_numeric( $i ) ) {
- $order = $i + 1;
- } else {
- $div['class'] .= ' acf-clone';
- }
- // display
- if ( $layout['display'] == 'table' ) {
- $el = 'td';
- }
- // title
- $title = $this->get_layout_title( $field, $layout, $i, $value );
- // remove row
- reset_rows();
- ?>
- <div <?php echo acf_esc_attr( $div ); ?>>
- <?php
- acf_hidden_input(
- array(
- 'name' => $prefix . '[acf_fc_layout]',
- 'value' => $layout['name'],
- )
- );
- ?>
- <div class="acf-fc-layout-handle" title="<?php _e( 'Drag to reorder', 'acf' ); ?>" data-name="collapse-layout"><?php echo acf_esc_html( $title ); ?></div>
- <div class="acf-fc-layout-controls">
- <a class="acf-icon -plus small light acf-js-tooltip" href="#" data-name="add-layout" title="<?php _e( 'Add layout', 'acf' ); ?>"></a>
- <a class="acf-icon -duplicate small light acf-js-tooltip" href="#" data-name="duplicate-layout" title="<?php _e( 'Duplicate layout', 'acf' ); ?>"></a>
- <a class="acf-icon -minus small light acf-js-tooltip" href="#" data-name="remove-layout" title="<?php _e( 'Remove layout', 'acf' ); ?>"></a>
- <a class="acf-icon -collapse small -clear acf-js-tooltip" href="#" data-name="collapse-layout" title="<?php _e( 'Click to toggle', 'acf' ); ?>"></a>
- </div>
- <?php if ( ! empty( $sub_fields ) ) : ?>
- <?php if ( $layout['display'] == 'table' ) : ?>
- <table class="acf-table">
- <thead>
- <tr>
- <?php
- foreach ( $sub_fields as $sub_field ) :
- // Set prefix to generate correct "for" attribute on <label>.
- $sub_field['prefix'] = $prefix;
- // Prepare field (allow sub fields to be removed).
- $sub_field = acf_prepare_field( $sub_field );
- if ( ! $sub_field ) {
- continue;
- }
- // Define attrs.
- $attrs = array();
- $attrs['class'] = 'acf-th';
- $attrs['data-name'] = $sub_field['_name'];
- $attrs['data-type'] = $sub_field['type'];
- $attrs['data-key'] = $sub_field['key'];
- if ( $sub_field['wrapper']['width'] ) {
- $attrs['data-width'] = $sub_field['wrapper']['width'];
- $attrs['style'] = 'width: ' . $sub_field['wrapper']['width'] . '%;';
- }
- ?>
- <th <?php echo acf_esc_attrs( $attrs ); ?>>
- <?php acf_render_field_label( $sub_field ); ?>
- <?php acf_render_field_instructions( $sub_field ); ?>
- </th>
- <?php endforeach; ?>
- </tr>
- </thead>
- <tbody>
- <tr class="acf-row">
- <?php else : ?>
- <div class="acf-fields
- <?php
- if ( $layout['display'] == 'row' ) :
- ?>
- -left<?php endif; ?>">
- <?php endif; ?>
- <?php
- // loop though sub fields
- foreach ( $sub_fields as $sub_field ) {
- // add value
- if ( isset( $value[ $sub_field['key'] ] ) ) {
- // this is a normal value
- $sub_field['value'] = $value[ $sub_field['key'] ];
- } elseif ( isset( $sub_field['default_value'] ) ) {
- // no value, but this sub field has a default value
- $sub_field['value'] = $sub_field['default_value'];
- }
- // update prefix to allow for nested values
- $sub_field['prefix'] = $prefix;
- // render input
- acf_render_field_wrap( $sub_field, $el );
- }
- ?>
- <?php if ( $layout['display'] == 'table' ) : ?>
- </tr>
- </tbody>
- </table>
- <?php else : ?>
- </div>
- <?php endif; ?>
- <?php endif; ?>
- </div>
- <?php
- }
- /**
- * Renders the flexible content field layouts in the field group editor.
- *
- * @since 3.6
- * @date 23/01/13
- *
- * @param array $field An array holding all the field's data.
- */
- public function render_field_settings( $field ) {
- $layout_open = apply_filters( 'acf/fields/flexible_content/layout_default_expanded', false );
- // Load default layout.
- if ( empty( $field['layouts'] ) ) {
- $layout_open = true;
- $field['layouts'] = array(
- array(),
- );
- }
- $field_settings_class = $layout_open ? 'open' : '';
- $toggle_class = $layout_open ? 'open' : 'closed';
- $field_settings_style = $layout_open ? '' : 'display: none;';
- // loop through layouts
- foreach ( $field['layouts'] as $layout ) {
- // get valid layout
- $layout = $this->get_valid_layout( $layout );
- // vars
- $layout_prefix = "{$field['prefix']}[layouts][{$layout['key']}]";
- ?>
- <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-id="<?php echo esc_attr( $layout['key'] ); ?>">
- <div class="acf-label acf-field-settings-fc_head">
- <div class="acf-fc_draggable">
- <label class="reorder-layout"><?php esc_attr_e( 'Layout', 'acf' ); ?>
- <span class="acf-fc-layout-name"></span></label>
- </div>
- <ul class="acf-bl acf-fl-actions">
- <li><button class="acf-btn acf-btn-tertiary acf-btn-sm acf-field-setting-fc-delete"><i class="acf-icon acf-icon-trash delete-layout " href="#" title="<?php esc_attr_e( 'Delete Layout', 'acf' ); ?>"></i></button></li>
- <li><button class="acf-btn acf-btn-tertiary acf-btn-sm acf-field-setting-fc-duplicate"><i class="acf-icon -duplicate duplicate-layout" href="#" title="<?php esc_attr_e( 'Duplicate Layout', 'acf' ); ?>"></i></button></li>
- <li class="acf-fc-add-layout"><button class="add-layout acf-btn acf-btn-primary add-field" href="#" title="<?php esc_attr_e( 'Add New Layout', 'acf' ); ?>"><i class="acf-icon acf-icon-plus"></i><?php esc_html_e( 'Add Layout', 'acf' ); ?></button></li>
- <li><button type="button" class="acf-toggle-fc-layout" aria-expanded="true"></li>
- <li><span class="toggle-indicator <?php echo esc_attr( $toggle_class ); ?>" aria-hidden="true"></span></li>
- </ul>
- </div>
- <div class="acf-input acf-field-layout-settings <?php echo esc_attr( $field_settings_class ); ?>" style="<?php echo esc_attr( $field_settings_style ); ?>">
- <?php
- acf_hidden_input(
- array(
- 'id' => acf_idify( $layout_prefix . '[key]' ),
- 'name' => $layout_prefix . '[key]',
- 'class' => 'layout-key',
- 'value' => $layout['key'],
- )
- );
- ?>
- <ul class="acf-fc-meta acf-bl">
- <li class="acf-fc-meta-label acf-fc-meta-left">
- <?php
- acf_render_field(
- array(
- 'type' => 'text',
- 'name' => 'label',
- 'class' => 'layout-label',
- 'prefix' => $layout_prefix,
- 'value' => $layout['label'],
- 'prepend' => __( 'Label', 'acf' ),
- )
- );
- ?>
- </li>
- <li class="acf-fc-meta-name acf-fc-meta-right">
- <?php
- acf_render_field(
- array(
- 'type' => 'text',
- 'name' => 'name',
- 'class' => 'layout-name',
- 'prefix' => $layout_prefix,
- 'value' => $layout['name'],
- 'prepend' => __( 'Name', 'acf' ),
- )
- );
- ?>
- </li>
- <li class="acf-fc-meta-display acf-fc-meta-left">
- <div class="acf-input-prepend"><?php esc_html_e( 'Layout', 'acf' ); ?></div>
- <div class="acf-input-wrap">
- <?php
- acf_render_field(
- array(
- 'type' => 'select',
- 'name' => 'display',
- 'prefix' => $layout_prefix,
- 'value' => $layout['display'],
- 'class' => 'acf-is-prepended',
- 'choices' => array(
- 'table' => __( 'Table', 'acf' ),
- 'block' => __( 'Block', 'acf' ),
- 'row' => __( 'Row', 'acf' ),
- ),
- )
- );
- ?>
- </div>
- </li>
- <li class="acf-fc-meta-min">
- <?php
- acf_render_field(
- array(
- 'type' => 'text',
- 'name' => 'min',
- 'prefix' => $layout_prefix,
- 'value' => $layout['min'],
- 'prepend' => __( 'Min', 'acf' ),
- )
- );
- ?>
- </li>
- <li class="acf-fc-meta-max">
- <?php
- acf_render_field(
- array(
- 'type' => 'text',
- 'name' => 'max',
- 'prefix' => $layout_prefix,
- 'value' => $layout['max'],
- 'prepend' => __( 'Max', 'acf' ),
- )
- );
- ?>
- </li>
- </ul>
- <div class="acf-input-sub">
- <?php
- // vars
- $args = array(
- 'fields' => $layout['sub_fields'],
- 'parent' => $field['ID'],
- 'is_subfield' => true,
- );
- acf_get_view( 'field-group-fields', $args );
- ?>
- </div>
- </div>
- </div>
- <?php
- }
- // endforeach
- }
- /**
- * Renders the field settings used in the "Presentation" tab.
- *
- * @since 6.0
- *
- * @param array $field The field settings array.
- * @return void
- */
- function render_field_presentation_settings( $field ) {
- // min
- acf_render_field_setting(
- $field,
- array(
- 'label' => __( 'Minimum Layouts', 'acf' ),
- 'instructions' => '',
- 'type' => 'number',
- 'name' => 'min',
- )
- );
- // max
- acf_render_field_setting(
- $field,
- array(
- 'label' => __( 'Maximum Layouts', 'acf' ),
- 'instructions' => '',
- 'type' => 'number',
- 'name' => 'max',
- )
- );
- // add new row label
- acf_render_field_setting(
- $field,
- array(
- 'label' => __( 'Button Label', 'acf' ),
- 'instructions' => '',
- 'type' => 'text',
- 'name' => 'button_label',
- )
- );
- }
- /*
- * load_value()
- *
- * This filter is applied to the $value after it is loaded from the db
- *
- * @type filter
- * @since 3.6
- * @date 23/01/13
- *
- * @param $value (mixed) the value found in the database
- * @param $post_id (mixed) the $post_id from which the value was loaded
- * @param $field (array) the field array holding all the field options
- * @return $value
- */
- function load_value( $value, $post_id, $field ) {
- // bail early if no value
- if ( empty( $value ) || empty( $field['layouts'] ) ) {
- return $value;
- }
- // value must be an array
- $value = acf_get_array( $value );
- // vars
- $rows = array();
- // sort layouts into names
- $layouts = array();
- foreach ( $field['layouts'] as $k => $layout ) {
- $layouts[ $layout['name'] ] = $layout['sub_fields'];
- }
- // loop through rows
- foreach ( $value as $i => $l ) {
- // append to $values
- $rows[ $i ] = array();
- $rows[ $i ]['acf_fc_layout'] = $l;
- // bail early if layout deosnt contain sub fields
- if ( empty( $layouts[ $l ] ) ) {
- continue;
- }
- // get layout
- $layout = $layouts[ $l ];
- // loop through sub fields
- foreach ( array_keys( $layout ) as $j ) {
- // get sub field
- $sub_field = $layout[ $j ];
- // bail early if no name (tab)
- if ( acf_is_empty( $sub_field['name'] ) ) {
- continue;
- }
- // update full name
- $sub_field['name'] = "{$field['name']}_{$i}_{$sub_field['name']}";
- // get value
- $sub_value = acf_get_value( $post_id, $sub_field );
- // add value
- $rows[ $i ][ $sub_field['key'] ] = $sub_value;
- }
- // foreach
- }
- // foreach
- // return
- return $rows;
- }
- /*
- * format_value()
- *
- * This filter is appied to the $value after it is loaded from the db and before it is returned to the template
- *
- * @type filter
- * @since 3.6
- * @date 23/01/13
- *
- * @param $value (mixed) the value which was loaded from the database
- * @param $post_id (mixed) the $post_id from which the value was loaded
- * @param $field (array) the field array holding all the field options
- *
- * @return $value (mixed) the modified value
- */
- function format_value( $value, $post_id, $field ) {
- // bail early if no value
- if ( empty( $value ) || empty( $field['layouts'] ) ) {
- return false;
- }
- // sort layouts into names
- $layouts = array();
- foreach ( $field['layouts'] as $k => $layout ) {
- $layouts[ $layout['name'] ] = $layout['sub_fields'];
- }
- // loop over rows
- foreach ( array_keys( $value ) as $i ) {
- // get layout name
- $l = $value[ $i ]['acf_fc_layout'];
- // bail early if layout deosnt exist
- if ( empty( $layouts[ $l ] ) ) {
- continue;
- }
- // get layout
- $layout = $layouts[ $l ];
- // loop through sub fields
- foreach ( array_keys( $layout ) as $j ) {
- // get sub field
- $sub_field = $layout[ $j ];
- // bail early if no name (tab)
- if ( acf_is_empty( $sub_field['name'] ) ) {
- continue;
- }
- // extract value
- $sub_value = acf_extract_var( $value[ $i ], $sub_field['key'] );
- // update $sub_field name
- $sub_field['name'] = "{$field['name']}_{$i}_{$sub_field['name']}";
- // format value
- $sub_value = acf_format_value( $sub_value, $post_id, $sub_field );
- // append to $row
- $value[ $i ][ $sub_field['_name'] ] = $sub_value;
- }
- }
- // return
- return $value;
- }
- /*
- * validate_value
- *
- * description
- *
- * @type function
- * @date 11/02/2014
- * @since 5.0.0
- *
- * @param $post_id (int)
- * @return $post_id (int)
- */
- function validate_value( $valid, $value, $field, $input ) {
- // vars
- $count = 0;
- // check if is value (may be empty string)
- if ( is_array( $value ) ) {
- // remove acfcloneindex
- if ( isset( $value['acfcloneindex'] ) ) {
- unset( $value['acfcloneindex'] );
- }
- // count
- $count = count( $value );
- }
- // validate required
- if ( $field['required'] && ! $count ) {
- $valid = false;
- }
- // validate min
- $min = (int) $field['min'];
- if ( $min && $count < $min ) {
- // vars
- $error = __( 'This field requires at least {min} {label} {identifier}', 'acf' );
- $identifier = _n( 'layout', 'layouts', $min );
- // replace
- $error = str_replace( '{min}', $min, $error );
- $error = str_replace( '{label}', '', $error );
- $error = str_replace( '{identifier}', $identifier, $error );
- // return
- return $error;
- }
- // find layouts
- $layouts = array();
- foreach ( array_keys( $field['layouts'] ) as $i ) {
- // vars
- $layout = $field['layouts'][ $i ];
- // add count
- $layout['count'] = 0;
- // append
- $layouts[ $layout['name'] ] = $layout;
- }
- // validate value
- if ( $count ) {
- // loop rows
- foreach ( $value as $i => $row ) {
- // get layout
- $l = $row['acf_fc_layout'];
- // bail if layout doesn't exist
- if ( ! isset( $layouts[ $l ] ) ) {
- continue;
- }
- // increase count
- $layouts[ $l ]['count']++;
- // bail if no sub fields
- if ( empty( $layouts[ $l ]['sub_fields'] ) ) {
- continue;
- }
- // loop sub fields
- foreach ( $layouts[ $l ]['sub_fields'] as $sub_field ) {
- // get sub field key
- $k = $sub_field['key'];
- // bail if no value
- if ( ! isset( $value[ $i ][ $k ] ) ) {
- continue;
- }
- // validate
- acf_validate_value( $value[ $i ][ $k ], $sub_field, "{$input}[{$i}][{$k}]" );
- }
- // end loop sub fields
- }
- // end loop rows
- }
- // validate layouts
- foreach ( $layouts as $layout ) {
- // validate min / max
- $min = (int) $layout['min'];
- $count = $layout['count'];
- $label = $layout['label'];
- if ( $min && $count < $min ) {
- // vars
- $error = __( 'This field requires at least {min} {label} {identifier}', 'acf' );
- $identifier = _n( 'layout', 'layouts', $min );
- // replace
- $error = str_replace( '{min}', $min, $error );
- $error = str_replace( '{label}', '"' . $label . '"', $error );
- $error = str_replace( '{identifier}', $identifier, $error );
- // return
- return $error;
- }
- }
- // return
- return $valid;
- }
- /**
- * This function will return a specific layout by name from a field
- *
- * @date 15/2/17
- * @since 5.5.8
- *
- * @param string $name
- * @param array $field
- * @return array|false
- */
- function get_layout( $name, $field ) {
- // bail early if no layouts
- if ( ! isset( $field['layouts'] ) ) {
- return false;
- }
- // loop
- foreach ( $field['layouts'] as $layout ) {
- // match
- if ( $layout['name'] === $name ) {
- return $layout;
- }
- }
- // return
- return false;
- }
- /**
- * This function will delete a value row
- *
- * @date 15/2/17
- * @since 5.5.8
- *
- * @param int $i
- * @param array $field
- * @param mixed $post_id
- * @return bool
- */
- function delete_row( $i, $field, $post_id ) {
- // vars
- $value = acf_get_metadata( $post_id, $field['name'] );
- // bail early if no value
- if ( ! is_array( $value ) || ! isset( $value[ $i ] ) ) {
- return false;
- }
- // get layout
- $layout = $this->get_layout( $value[ $i ], $field );
- // bail early if no layout
- if ( ! $layout || empty( $layout['sub_fields'] ) ) {
- return false;
- }
- // loop
- foreach ( $layout['sub_fields'] as $sub_field ) {
- // modify name for delete
- $sub_field['name'] = "{$field['name']}_{$i}_{$sub_field['name']}";
- // delete value
- acf_delete_value( $post_id, $sub_field );
- }
- // return
- return true;
- }
- /**
- * This function will update a value row
- *
- * @date 15/2/17
- * @since 5.5.8
- *
- * @param array $row
- * @param int $i
- * @param array $field
- * @param mixed $post_id
- * @return bool
- */
- function update_row( $row, $i, $field, $post_id ) {
- // bail early if no layout reference
- if ( ! is_array( $row ) || ! isset( $row['acf_fc_layout'] ) ) {
- return false;
- }
- // get layout
- $layout = $this->get_layout( $row['acf_fc_layout'], $field );
- // bail early if no layout
- if ( ! $layout || empty( $layout['sub_fields'] ) ) {
- return false;
- }
- foreach ( $layout['sub_fields'] as $sub_field ) {
- $value = null;
- if ( array_key_exists( $sub_field['key'], $row ) ) {
- $value = $row[ $sub_field['key'] ];
- } elseif ( array_key_exists( $sub_field['name'], $row ) ) {
- $value = $row[ $sub_field['name'] ];
- } else {
- // Value does not exist.
- continue;
- }
- // modify name for save
- $sub_field['name'] = "{$field['name']}_{$i}_{$sub_field['name']}";
- // update field
- acf_update_value( $value, $post_id, $sub_field );
- }
- return true;
- }
- /*
- * update_value()
- *
- * This filter is appied to the $value before it is updated in the db
- *
- * @type filter
- * @since 3.6
- * @date 23/01/13
- *
- * @param $value - the value which will be saved in the database
- * @param $field - the field array holding all the field options
- * @param $post_id - the $post_id of which the value will be saved
- *
- * @return $value - the modified value
- */
- function update_value( $value, $post_id, $field ) {
- // bail early if no layouts
- if ( empty( $field['layouts'] ) ) {
- return $value;
- }
- // vars
- $new_value = array();
- $old_value = acf_get_metadata( $post_id, $field['name'] );
- $old_value = is_array( $old_value ) ? $old_value : array();
- // update
- if ( ! empty( $value ) ) {
- $i = -1;
- // remove acfcloneindex
- if ( isset( $value['acfcloneindex'] ) ) {
- unset( $value['acfcloneindex'] );
- }
- // loop through rows
- foreach ( $value as $row ) {
- $i++;
- // bail early if no layout reference
- if ( ! is_array( $row ) || ! isset( $row['acf_fc_layout'] ) ) {
- continue;
- }
- // delete old row if layout has changed
- if ( isset( $old_value[ $i ] ) && $old_value[ $i ] !== $row['acf_fc_layout'] ) {
- $this->delete_row( $i, $field, $post_id );
- }
- // update row
- $this->update_row( $row, $i, $field, $post_id );
- // append to order
- $new_value[] = $row['acf_fc_layout'];
- }
- }
- // vars
- $old_count = empty( $old_value ) ? 0 : count( $old_value );
- $new_count = empty( $new_value ) ? 0 : count( $new_value );
- // remove old rows
- if ( $old_count > $new_count ) {
- // loop
- for ( $i = $new_count; $i < $old_count; $i++ ) {
- $this->delete_row( $i, $field, $post_id );
- }
- }
- // save false for empty value
- if ( empty( $new_value ) ) {
- $new_value = '';
- }
- // return
- return $new_value;
- }
- /*
- * delete_value
- *
- * description
- *
- * @type function
- * @date 1/07/2015
- * @since 5.2.3
- *
- * @param $post_id (int)
- * @return $post_id (int)
- */
- function delete_value( $post_id, $key, $field ) {
- // vars
- $old_value = acf_get_metadata( $post_id, $field['name'] );
- $old_value = is_array( $old_value ) ? $old_value : array();
- // bail early if no rows or no sub fields
- if ( empty( $old_value ) ) {
- return;
- }
- // loop
- foreach ( array_keys( $old_value ) as $i ) {
- $this->delete_row( $i, $field, $post_id );
- }
- }
- /*
- * update_field()
- *
- * This filter is appied to the $field before it is saved to the database
- *
- * @type filter
- * @since 3.6
- * @date 23/01/13
- *
- * @param $field - the field array holding all the field options
- * @param $post_id - the field group ID (post_type = acf)
- *
- * @return $field - the modified field
- */
- function update_field( $field ) {
- // loop
- if ( ! empty( $field['layouts'] ) ) {
- foreach ( $field['layouts'] as &$layout ) {
- unset( $layout['sub_fields'] );
- }
- }
- // return
- return $field;
- }
- /*
- * delete_field
- *
- * description
- *
- * @type function
- * @date 4/04/2014
- * @since 5.0.0
- *
- * @param $post_id (int)
- * @return $post_id (int)
- */
- function delete_field( $field ) {
- if ( ! empty( $field['layouts'] ) ) {
- // loop through layouts
- foreach ( $field['layouts'] as $layout ) {
- // loop through sub fields
- if ( ! empty( $layout['sub_fields'] ) ) {
- foreach ( $layout['sub_fields'] as $sub_field ) {
- acf_delete_field( $sub_field['ID'] );
- }
- // foreach
- }
- // if
- }
- // foreach
- }
- // if
- }
- /*
- * duplicate_field()
- *
- * This filter is appied to the $field before it is duplicated and saved to the database
- *
- * @type filter
- * @since 3.6
- * @date 23/01/13
- *
- * @param $field - the field array holding all the field options
- *
- * @return $field - the modified field
- */
- function duplicate_field( $field ) {
- // vars
- $sub_fields = array();
- if ( ! empty( $field['layouts'] ) ) {
- // loop through layouts
- foreach ( $field['layouts'] as $layout ) {
- // extract sub fields
- $extra = acf_extract_var( $layout, 'sub_fields' );
- // merge
- if ( ! empty( $extra ) ) {
- $sub_fields = array_merge( $sub_fields, $extra );
- }
- }
- // foreach
- }
- // if
- // save field to get ID
- $field = acf_update_field( $field );
- // duplicate sub fields
- acf_duplicate_fields( $sub_fields, $field['ID'] );
- // return
- return $field;
- }
- /*
- * ajax_layout_title
- *
- * description
- *
- * @type function
- * @date 2/03/2016
- * @since 5.3.2
- *
- * @param $post_id (int)
- * @return $post_id (int)
- */
- function ajax_layout_title() {
- $options = acf_parse_args(
- $_POST, // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Verified elsewhere.
- array(
- 'post_id' => 0,
- 'i' => 0,
- 'field_key' => '',
- 'nonce' => '',
- 'layout' => '',
- 'value' => array(),
- )
- );
- // load field
- $field = acf_get_field( $options['field_key'] );
- if ( ! $field ) {
- die();
- }
- // vars
- $layout = $this->get_layout( $options['layout'], $field );
- if ( ! $layout ) {
- die();
- }
- // title
- $title = $this->get_layout_title( $field, $layout, $options['i'], $options['value'] );
- // echo
- echo $title;
- die;
- }
- function get_layout_title( $field, $layout, $i, $value ) {
- // vars
- $rows = array();
- $rows[ $i ] = $value;
- // add loop
- acf_add_loop(
- array(
- 'selector' => $field['name'],
- 'name' => $field['name'],
- 'value' => $rows,
- 'field' => $field,
- 'i' => $i,
- 'post_id' => 0,
- )
- );
- // vars
- $title = $layout['label'];
- // filters
- $title = apply_filters( 'acf/fields/flexible_content/layout_title', $title, $field, $layout, $i );
- $title = apply_filters( 'acf/fields/flexible_content/layout_title/name=' . $field['_name'], $title, $field, $layout, $i );
- $title = apply_filters( 'acf/fields/flexible_content/layout_title/key=' . $field['key'], $title, $field, $layout, $i );
- // remove loop
- acf_remove_loop();
- // prepend order
- $order = is_numeric( $i ) ? $i + 1 : 0;
- $title = '<span class="acf-fc-layout-order">' . $order . '</span> ' . acf_esc_html( $title );
- // return
- return $title;
- }
- /*
- * clone_any_field
- *
- * This function will update clone field settings based on the origional field
- *
- * @type function
- * @date 28/06/2016
- * @since 5.3.8
- *
- * @param $clone (array)
- * @param $field (array)
- * @return $clone
- */
- function clone_any_field( $field, $clone_field ) {
- // remove parent_layout
- // - allows a sub field to be rendered as a normal field
- unset( $field['parent_layout'] );
- // attempt to merger parent_layout
- if ( isset( $clone_field['parent_layout'] ) ) {
- $field['parent_layout'] = $clone_field['parent_layout'];
- }
- // return
- return $field;
- }
- /*
- * prepare_field_for_export
- *
- * description
- *
- * @type function
- * @date 11/03/2014
- * @since 5.0.0
- *
- * @param $post_id (int)
- * @return $post_id (int)
- */
- function prepare_field_for_export( $field ) {
- // loop
- if ( ! empty( $field['layouts'] ) ) {
- foreach ( $field['layouts'] as &$layout ) {
- $layout['sub_fields'] = acf_prepare_fields_for_export( $layout['sub_fields'] );
- }
- }
- // return
- return $field;
- }
- function prepare_any_field_for_export( $field ) {
- // remove parent_layout
- unset( $field['parent_layout'] );
- // return
- return $field;
- }
- /*
- * prepare_field_for_import
- *
- * description
- *
- * @type function
- * @date 11/03/2014
- * @since 5.0.0
- *
- * @param $post_id (int)
- * @return $post_id (int)
- */
- function prepare_field_for_import( $field ) {
- // Bail early if no layouts
- if ( empty( $field['layouts'] ) ) {
- return $field;
- }
- // Storage for extracted fields.
- $extra = array();
- // Loop over layouts.
- foreach ( $field['layouts'] as &$layout ) {
- // Ensure layout is valid.
- $layout = $this->get_valid_layout( $layout );
- // Extract sub fields.
- $sub_fields = acf_extract_var( $layout, 'sub_fields' );
- // Modify and append sub fields to $extra.
- if ( $sub_fields ) {
- foreach ( $sub_fields as $i => $sub_field ) {
- // Update atttibutes
- $sub_field['parent'] = $field['key'];
- $sub_field['parent_layout'] = $layout['key'];
- $sub_field['menu_order'] = $i;
- // Append to extra.
- $extra[] = $sub_field;
- }
- }
- }
- // Merge extra sub fields.
- if ( $extra ) {
- array_unshift( $extra, $field );
- return $extra;
- }
- // Return field.
- return $field;
- }
- /*
- * validate_any_field
- *
- * This function will add compatibility for the 'column_width' setting
- *
- * @type function
- * @date 30/1/17
- * @since 5.5.6
- *
- * @param $field (array)
- * @return $field
- */
- function validate_any_field( $field ) {
- // width has changed
- if ( isset( $field['column_width'] ) ) {
- $field['wrapper']['width'] = acf_extract_var( $field, 'column_width' );
- }
- // return
- return $field;
- }
- /*
- * translate_field
- *
- * This function will translate field settings
- *
- * @type function
- * @date 8/03/2016
- * @since 5.3.2
- *
- * @param $field (array)
- * @return $field
- */
- function translate_field( $field ) {
- // translate
- $field['button_label'] = acf_translate( $field['button_label'] );
- // loop
- if ( ! empty( $field['layouts'] ) ) {
- foreach ( $field['layouts'] as &$layout ) {
- $layout['label'] = acf_translate( $layout['label'] );
- }
- }
- // return
- return $field;
- }
- /**
- * Additional validation for the flexible content field when submitted via REST.
- *
- * @param bool $valid
- * @param int $value
- * @param array $field
- *
- * @return bool|WP_Error
- */
- public function validate_rest_value( $valid, $value, $field ) {
- $param = sprintf( '%s[%s]', $field['prefix'], $field['name'] );
- $data = array(
- 'param' => $param,
- 'value' => $value,
- );
- if ( ! is_array( $value ) && is_null( $value ) ) {
- $error = sprintf( __( '%s must be of type array or null.', 'acf' ), $param );
- return new WP_Error( 'rest_invalid_param', $error, $param );
- }
- $layouts_to_update = array_count_values( array_column( $value, 'acf_fc_layout' ) );
- foreach ( $field['layouts'] as $layout ) {
- $num_layouts = isset( $layouts_to_update[ $layout['name'] ] ) ? $layouts_to_update[ $layout['name'] ] : 0;
- if ( '' !== $layout['min'] && $num_layouts < (int) $layout['min'] ) {
- $error = sprintf(
- _n(
- '%1$s must contain at least %2$s %3$s layout.',
- '%1$s must contain at least %2$s %3$s layouts.',
- $layout['min'],
- 'acf'
- ),
- $param,
- number_format_i18n( $layout['min'] ),
- $layout['name']
- );
- return new WP_Error( 'rest_invalid_param', $error, $data );
- }
- if ( '' !== $layout['max'] && $num_layouts > (int) $layout['max'] ) {
- $error = sprintf(
- _n(
- '%1$s must contain at most %2$s %3$s layout.',
- '%1$s must contain at most %2$s %3$s layouts.',
- $layout['max'],
- 'acf'
- ),
- $param,
- number_format_i18n( $layout['max'] ),
- $layout['name']
- );
- return new WP_Error( 'rest_invalid_param', $error, $data );
- }
- }
- return $valid;
- }
- /**
- * Return the schema array for the REST API.
- *
- * @param array $field
- * @return array
- */
- public function get_rest_schema( array $field ) {
- $schema = array(
- 'type' => array( 'array', 'null' ),
- 'required' => ! empty( $field['required'] ),
- 'items' => array(
- 'oneOf' => array(),
- ),
- );
- // Loop through layouts building up a schema for each.
- foreach ( $field['layouts'] as $layout ) {
- $layout_schema = array(
- 'type' => 'object',
- 'properties' => array(
- 'acf_fc_layout' => array(
- 'type' => 'string',
- 'required' => true,
- // By using a pattern match against the layout name, data sent in must match an available
- // layout on the flexible field. If it doesn't, a 400 Bad Request response will result.
- 'pattern' => '^' . $layout['name'] . '$',
- ),
- ),
- );
- foreach ( $layout['sub_fields'] as $sub_field ) {
- if ( $sub_field_schema = acf_get_field_rest_schema( $sub_field ) ) {
- $layout_schema['properties'][ $sub_field['name'] ] = $sub_field_schema;
- }
- }
- $schema['items']['oneOf'][] = $layout_schema;
- }
- if ( ! empty( $field['min'] ) ) {
- $schema['minItems'] = (int) $field['min'];
- }
- if ( ! empty( $field['max'] ) ) {
- $schema['maxItems'] = (int) $field['max'];
- }
- return $schema;
- }
- /**
- * Apply basic formatting to prepare the value for default REST output.
- *
- * @param mixed $value
- * @param int|string $post_id
- * @param array $field
- * @return array|mixed
- */
- public function format_value_for_rest( $value, $post_id, array $field ) {
- if ( empty( $value ) || ! is_array( $value ) || empty( $field['layouts'] ) ) {
- return null;
- }
- // Create a map of layout sub fields mapped to layout names.
- foreach ( $field['layouts'] as $layout ) {
- $layouts[ $layout['name'] ] = $layout['sub_fields'];
- }
- // Loop through each layout and within that, each sub field to process sub fields individually.
- foreach ( $value as &$layout ) {
- $name = $layout['acf_fc_layout'];
- if ( empty( $layouts[ $name ] ) ) {
- continue;
- }
- foreach ( $layouts[ $name ] as $sub_field ) {
- // Bail early if the field has no name (tab).
- if ( acf_is_empty( $sub_field['name'] ) ) {
- continue;
- }
- // Extract the sub field 'field_key'=>'value' pair from the $layout and format it.
- $sub_value = acf_extract_var( $layout, $sub_field['key'] );
- $sub_value = acf_format_value_for_rest( $sub_value, $post_id, $sub_field );
- // Add the sub field value back to the $layout but mapped to the field name instead
- // of the key reference.
- $layout[ $sub_field['name'] ] = $sub_value;
- }
- }
- return $value;
- }
- }
- // initialize
- acf_register_field_type( 'acf_field_flexible_content' );
- endif; // class_exists check
- ?>
|