name = 'flexible_content';
$this->label = __("Flexible Content",'acf');
$this->category = 'layout';
$this->defaults = array(
'layouts' => array(),
'min' => '',
'max' => '',
'button_label' => __("Add Row",'acf'),
);
$this->l10n = array(
'layout' => __("layout", 'acf'),
'layouts' => __("layouts", 'acf'),
'remove' => __("remove {layout}?", 'acf'),
'min' => __("This field requires at least {min} {identifier}",'acf'),
'max' => __("This field has a limit of {max} {identifier}",'acf'),
'min_layout' => __("This field requires at least {min} {label} {identifier}",'acf'),
'max_layout' => __("Maximum {label} limit reached ({max} {identifier})",'acf'),
'available' => __("{available} {label} {identifier} available (max {max})",'acf'),
'required' => __("{required} {label} {identifier} required (min {min})",'acf'),
);
// ajax
add_action('wp_ajax_acf/fields/flexible_content/layout_title', array($this, 'ajax_layout_title'));
add_action('wp_ajax_nopriv_acf/fields/flexible_content/layout_title', array($this, 'ajax_layout_title'));
// filters
add_filter('acf/clone_field', array($this, 'acf_clone_field'), 10, 2);
// field filters
$this->add_field_filter('acf/get_sub_field', array($this, 'get_sub_field'), 10, 3);
// do not delete!
parent::__construct();
}
/*
* 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(),
'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, $selector, $field ) {
// bail early if no layouts
if( empty($field['layouts']) ) return false;
// vars
$active = get_row_layout();
// loop
foreach( $field['layouts'] as $layout ) {
// bail early if active layout does not match
if( $active && $active !== $layout['name'] ) continue;
// bail early if no sub fields
if( empty($layout['sub_fields']) ) continue;
// loop
foreach( $layout['sub_fields'] as $sub_field ) {
// check name and key
if( $sub_field['name'] == $selector || $sub_field['key'] == $selector ) {
// return
return $sub_field;
}
}
}
// return
return false;
}
/*
* 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;
}
// hidden input
acf_hidden_input(array(
'type' => 'hidden',
'name' => $field['name'],
));
// 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);
?>
'acf-flexible-content', 'data-min' => $field['min'], 'data-max' => $field['max'] )); ?>>
>
render_layout( $field, $layout, 'acfcloneindex', array() ); ?>
$value ): ?>
render_layout( $field, $layouts[ $value['acf_fc_layout'] ], $i, $value );
?>
'layout',
'data-id' => $i,
'data-layout' => $layout['name']
);
// collapsed class
if( acf_is_row_collapsed($field['key'], $i) ) {
$div['class'] .= ' -collapsed';
}
// clone
if( is_numeric($i) ) {
$order = $i + 1;
} else {
$div['class'] .= ' acf-clone';
}
// title
$title = $this->get_layout_title( $field, $layout, $i, $value );
// remove row
reset_rows();
?>
>
"{$field['name']}[{$i}][acf_fc_layout]", 'value' => $layout['name'] )); ?>
"acf-th acf-th-{$sub_field['name']}",
'data-key' => $sub_field['key'],
);
// Add custom width
if( $sub_field['wrapper']['width'] ) {
$atts['data-width'] = $sub_field['wrapper']['width'];
}
?>
| >
|
get_valid_layout( $layout );
// vars
$layout_prefix = "{$field['prefix']}[layouts][{$layout['key']}]";
?>
|
" >
" href="#">
" href="#">
" href="#">
|
$layout['sub_fields'],
'layout' => $layout['display'],
'parent' => $field['ID']
);
acf_get_view('field-group-fields', $args);
?>
|
__('Button Label','acf'),
'instructions' => '',
'type' => 'text',
'name' => 'button_label',
));
// 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',
));
}
/*
* 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();
// populate $layouts
$layouts = array();
foreach( array_keys($field['layouts']) as $i ) {
// get layout
$layout = $field['layouts'][ $i ];
// append to $layouts
$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 ealry 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;
}
// populate $layouts
$layouts = array();
foreach( array_keys($field['layouts']) as $i ) {
// get layout
$layout = $field['layouts'][ $i ];
// append to $layouts
$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 ealry 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 ){
// remove acfcloneindex
if( isset($value['acfcloneindex']) ) {
unset($value['acfcloneindex']);
}
// valid
if( $field['required'] && empty($value) ) {
$valid = false;
}
// populate $layouts
$layouts = array();
foreach( array_keys($field['layouts']) as $i ) {
$layout = acf_extract_var($field['layouts'], $i);
// append to $layouts
$layouts[ $layout['name'] ] = $layout['sub_fields'];
}
// check sub fields
if( !empty($value) ) {
// loop through rows
foreach( $value as $i => $row ) {
// get layout
$l = $row['acf_fc_layout'];
// loop through sub fields
if( !empty($layouts[ $l ]) ) {
foreach( $layouts[ $l ] as $sub_field ) {
// get sub field key
$k = $sub_field['key'];
// exists?
if( ! isset($value[ $i ][ $k ]) ) {
continue;
}
// validate
acf_validate_value( $value[ $i ][ $k ], $sub_field, "{$input}[{$i}][{$k}]" );
}
// foreach
}
// if
}
// foreach
}
// if
// return
return $valid;
}
/*
* 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 ) {
// remove acfcloneindex
if( isset($value['acfcloneindex']) ) {
unset($value['acfcloneindex']);
}
// vars
$order = array();
$layouts = array();
// populate $layouts
foreach( $field['layouts'] as $layout ) {
$layouts[ $layout['name'] ] = $layout['sub_fields'];
}
// update sub fields
if( !empty($value) ) {
// $i
$i = -1;
// loop through rows
foreach( $value as $row ) {
// $i
$i++;
// get layout
$l = $row['acf_fc_layout'];
// append to order
$order[] = $l;
// loop through sub fields
if( !empty($layouts[ $l ]) ) {
foreach( $layouts[ $l ] as $sub_field ) {
// value
$v = false;
// key (backend)
if( isset($row[ $sub_field['key'] ]) ) {
$v = $row[ $sub_field['key'] ];
} elseif( isset($row[ $sub_field['name'] ]) ) {
$v = $row[ $sub_field['name'] ];
} else {
// input is not set (hidden by conditioanl logic)
continue;
}
// modify name for save
$sub_field['name'] = "{$field['name']}_{$i}_{$sub_field['name']}";
// update field
acf_update_value( $v, $post_id, $sub_field );
}
// foreach
}
// if
}
// foreach
}
// if
// remove old data
$old_order = acf_get_metadata( $post_id, $field['name'] );
$old_count = empty($old_order) ? 0 : count($old_order);
$new_count = empty($order) ? 0 : count($order);
if( $old_count > $new_count ) {
for( $i = $new_count; $i < $old_count; $i++ ) {
// get layout
$l = $old_order[ $i ];
// loop through sub fields
if( !empty($layouts[ $l ]) ) {
foreach( $layouts[ $l ] 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 );
}
}
}
}
// save false for empty value
if( empty($order) ) {
$order = '';
}
// return
return $order;
}
/*
* 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 ) {
// get old value (db only)
$old_order = acf_get_metadata( $post_id, $field['name'] );
// bail early if no rows or no sub fields
if( empty($old_order) ) return;
// vars
$layouts = array();
// populate $layouts
foreach( $field['layouts'] as $layout ) {
$layouts[ $layout['name'] ] = $layout['sub_fields'];
}
// loop
foreach( $old_order as $i => $l ) {
// bail early if no layout
if( empty($layouts[ $l ]) ) continue;
// loop through sub fields
foreach( $layouts[ $l ] as $sub_field ) {
// modify name for delete
$sub_field['name'] = "{$key}_{$i}_{$sub_field['name']}";
// delete value
acf_delete_value( $post_id, $sub_field );
}
}
}
/*
* 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 ) {
// vars
$layouts = acf_extract_var($field, 'layouts');
// update layouts
$field['layouts'] = array();
// loop through sub fields
if( !empty($layouts) ) {
foreach( $layouts as $layout ) {
// remove sub fields
unset($layout['sub_fields']);
// append to layouts
$field['layouts'][] = $layout;
}
}
// 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
$options = acf_parse_args( $_POST, array(
'post_id' => 0,
'i' => 0,
'field_key' => '',
'nonce' => '',
'layout' => '',
'acf' => array()
));
// load field
$field = acf_get_field( $options['field_key'] );
if( !$field ) die();
// vars
$layout = false;
foreach( $field['layouts'] as $k => $layout ) {
if( $layout['name'] === $options['layout'] ) break;
}
// bail ealry if no layout
if( !$layout ) die();
// value
// this flexible content field may be a sub field so it is important to
// loop though all $_POST data to find thi's field's row value
$value = $options['acf'];
while( is_array($value) ) {
// move to end of array
// - avoids 'acf_fc_layout' value
end( $value );
// vars (step through array)
$key = key($value);
$value = current($value);
// stop looking if we have found the correct field's value
if( $key === $options['field_key'] ) {
// get row
$value = current($value);
break;
}
}
// title
$title = $this->get_layout_title( $field, $layout, $options['i'], $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
$title = '' . ($i+1) . ' ' . $title;
// return
return $title;
}
/*
* acf_clone_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 acf_clone_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;
}
}
// initialize
acf_register_field_type( new acf_field_flexible_content() );
endif; // class_exists check
?>