settings = array(
'version' => ACF_TABLE_FIELD_PLUGIN_VERSION,
);
/*
* name (string) Single word, no spaces. Underscores allowed
*/
$this->name = 'table';
$this->description = __('This allows you to easily edit tabular content.', 'advanced-custom-fields-table-field');
$this->preview_image = FileSystem::get('plugin_dir_url') . '/assets/images/field-preview-table.png';
$this->doc_url = 'https://wordpress.org/plugins/advanced-custom-fields-table-field/';
/*
* label (string) Multiple words, can include spaces, visible when selecting a field type
*/
$this->label = __('Table', 'advanced-custom-fields-table-field');
/*
* category (string) basic | content | choice | relational | jquery | layout | CUSTOM GROUP NAME
*/
$this->category = 'content';
/*
* defaults (array) Array of default settings which are merged into the field object. These are used later in settings
*/
$this->defaults = array(
//'font_size' => 14,
);
/*
* l10n (array) Array of strings that are used in JavaScript. This allows JS strings to be translated in PHP and loaded via:
* var message = acf._e('table', 'error');
*/
$this->l10n = array(
//'error' => __('Error! Please enter a higher value.', 'advanced-custom-fields-table-field'),
);
// do not delete!
parent::__construct();
// PREVENTS SAVING INVALID TABLE FIELD JSON DATA {
if (
! defined( 'ACF_TABLEFIELD_FILTER_POSTMETA' ) OR
constant( 'ACF_TABLEFIELD_FILTER_POSTMETA' ) === true
) {
add_filter( 'update_post_metadata', function( $x, $object_id, $meta_key, $meta_value, $prev_value ) {
// detecting ACF table json
if (
is_string( $meta_value ) and
strpos( $meta_value, '"acftf":{' ) !== false
) {
// is new value a valid json string
json_decode( $meta_value );
if ( json_last_error() !== JSON_ERROR_NONE ) {
// canceling meta value uptdate
$error_message = 'The plugin advanced-custom-fields-table-field prevented a third party update_post_meta( ' . $object_id . ', "' . $meta_key . '", $value ); action that would save a broken JSON string.' . "\n" . 'For details see https://codex.wordpress.org/Function_Reference/update_post_meta#Character_Escaping.';
trigger_error( esc_html( $error_message ), E_USER_WARNING );
return true;
}
}
return $x;
}, 10, 5 );
}
// }
}
/*
* render_field_settings()
*
* Create extra settings for your field. These are visible when editing a field
*
* @type action
* @since 3.6
* @date 23/01/13
*
* @param $field (array) the $field being edited
* @return n/a
*/
function render_field_settings( $field ) {
/*
* acf_render_field_setting
*
* This function will create a setting for your field. Simply pass the $field parameter and an array of field settings.
* The array of settings does not require a `value` or `prefix`; These settings are found from the $field array.
*
* More than one setting can be added by copy/paste the above code.
* Please note that you must also have a matching $defaults value for the field name (font_size)
*/
acf_render_field_setting( $field, array(
'label' => __('Table Header','advanced-custom-fields-table-field'),
'instructions' => __('Presetting the usage of table header','advanced-custom-fields-table-field'),
'type' => 'radio',
'name' => 'use_header',
'choices' => array(
0 => __( "Optional", 'advanced-custom-fields-table-field' ),
1 => __( "Yes", 'advanced-custom-fields-table-field' ),
2 => __( "No", 'advanced-custom-fields-table-field' ),
),
'layout' => 'horizontal',
'default_value' => 0,
));
acf_render_field_setting( $field, array(
'label' => __('Table Caption','advanced-custom-fields-table-field'),
'instructions' => __('Presetting the usage of table caption','advanced-custom-fields-table-field'),
'type' => 'radio',
'name' => 'use_caption',
'choices' => array(
1 => __( "Yes", 'advanced-custom-fields-table-field' ),
2 => __( "No", 'advanced-custom-fields-table-field' ),
),
'layout' => 'horizontal',
'default_value' => 2,
));
}
/*
* render_field()
*
* Create the HTML interface for your field
*
* @param $field (array) the $field being rendered
*
* @type action
* @since 3.6
* @date 23/01/13
*
* @param $field (array) the $field being edited
* @return n/a
*/
function render_field( $field ) {
/*
* Review the data of $field.
* This will show what data is available
*/
if ( empty( $field['use_header'] ) ) {
$field['use_header'] = 0;
}
if ( empty( $field['use_caption'] ) ) {
$field['use_caption'] = 0;
}
$data_field['use_header'] = $field['use_header'];
$data_field['use_caption'] = $field['use_caption'];
$e = '';
$e .= '
';
$e .= '
';
// OPTION HEADER {
if ( $data_field['use_header'] === 0 ) {
$id = uniqid();
$e .= '
';
$e .= '' . __( 'use table header', 'advanced-custom-fields-table-field' ) . ' ';
$e .= '';
$e .= '
';
}
// }
// OPTION CAPTION {
if ( $data_field['use_caption'] === 1 ) {
$id = uniqid();
$e .= '
';
$e .= '' . __( 'Table Caption', 'advanced-custom-fields-table-field' ) . ' ';
$e .= ' ';
$e .= '
';
}
// }
$e .= '
';
if ( is_array( $field['value'] ) ) {
$field['value'] = wp_json_encode( $field['value'] );
}
if ( is_string( $field['value'] ) ) {
if ( substr( $field['value'] , 0 , 1 ) === '{' ) {
$field['value'] = urlencode( $field['value'] );
}
}
$e .= '
';
$e .= ' ';
$e .= '
';
$e .= '
';
echo $e;
}
/*
* input_admin_enqueue_scripts()
*
* This action is called in the admin_enqueue_scripts action on the edit screen where your field is created.
* Use this action to add CSS + JavaScript to assist your render_field() action.
*
* @type action (admin_enqueue_scripts)
* @since 3.6
* @date 23/01/13
*
* @param n/a
* @return n/a
*/
function input_admin_enqueue_scripts() {
// register & include JS
wp_enqueue_script( 'acf-input-table', FileSystem::get('plugin_dir_url') . 'js/init.js', array( 'jquery', 'acf-input' ), $this->settings['version'], true );
wp_enqueue_script( 'acf-input-table-execute', FileSystem::get('plugin_dir_url') . 'js/execute.js', array( 'acf-input-table' ), $this->settings['version'], true );
// register & include CSS
wp_register_style( 'acf-input-table', FileSystem::get('plugin_dir_url') . 'css/input.css', array( 'acf-input' ), $this->settings['version'] );
wp_enqueue_style( 'acf-input-table' );
}
/*
* input_admin_head()
*
* This action is called in the admin_head action on the edit screen where your field is created.
* Use this action to add CSS and JavaScript to assist your render_field() action.
*
* @type action (admin_head)
* @since 3.6
* @date 23/01/13
*
* @param n/a
* @return n/a
*/
/*
function input_admin_head() {
}
*/
/*
* input_form_data()
*
* This function is called once on the 'input' page between the head and footer
* There are 2 situations where ACF did not load during the 'acf/input_admin_enqueue_scripts' and
* 'acf/input_admin_head' actions because ACF did not know it was going to be used. These situations are
* seen on comments / user edit forms on the front end. This function will always be called, and includes
* $args that related to the current screen such as $args['post_id']
*
* @type function
* @date 6/03/2014
* @since 5.0.0
*
* @param $args (array)
* @return n/a
*/
/*
function input_form_data( $args ) {
}
*/
/*
* input_admin_footer()
*
* This action is called in the admin_footer action on the edit screen where your field is created.
* Use this action to add CSS and JavaScript to assist your render_field() action.
*
* @type action (admin_footer)
* @since 3.6
* @date 23/01/13
*
* @param n/a
* @return n/a
*/
/*
function input_admin_footer() {
}
*/
/*
* field_group_admin_enqueue_scripts()
*
* This action is called in the admin_enqueue_scripts action on the edit screen where your field is edited.
* Use this action to add CSS + JavaScript to assist your render_field_options() action.
*
* @type action (admin_enqueue_scripts)
* @since 3.6
* @date 23/01/13
*
* @param n/a
* @return n/a
*/
/*
function field_group_admin_enqueue_scripts() {
}
*/
/*
* field_group_admin_head()
*
* This action is called in the admin_head action on the edit screen where your field is edited.
* Use this action to add CSS and JavaScript to assist your render_field_options() action.
*
* @type action (admin_head)
* @since 3.6
* @date 23/01/13
*
* @param n/a
* @return n/a
*/
/*
function field_group_admin_head() {
}
*/
/*
* 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 ) {
return $value;
}
*/
/*
* update_value()
*
* This filter is applied to the $value before it is saved in 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 update_value( $value, $post_id, $field ) {
// DECODES URL ENCODED JSON STRING VALUES {
if ( is_string( $value ) ) {
$value = wp_unslash( $value );
$value = urldecode( $value );
$value = json_decode( $value, true );
}
// }
// REJECTS INVALID VALUE TYPES {
if ( ! is_array( $value ) ) {
return null;
}
if ( empty( $value ) ) {
return null;
}
// }
// GETS FIELD DATA FROM DATABASE {
$data = array();
// Gets type of source
if ( function_exists( 'acf_decode_post_id' ) ) {
$info = acf_decode_post_id( $post_id );
if ( $info['type'] === 'block' ) {
// Gets post to parse blocks data
$post = get_post();
if ( isset( $post->post_content ) ) {
$blocks = parse_blocks( $post->post_content );
if ( ! empty( $blocks ) ) {
foreach( $blocks as $block ) {
/**
* Continue when is no real block
* Not just editable blocks are parsed as blocks, but also other post content.
* Only real blocks have a blockName. Others have NULL as blockName value.
*/
if ( ! is_string( $block['blockName'] ) ) {
continue;
}
// Continue when is not a ACF blocks
if (
strpos( $block['blockName'], 'acf/' ) !== 0 ||
empty( $block['attrs'] )
) {
continue;
}
// Try if ACF block has value
if ( isset( $block['attrs']['data'][ $field['name'] ] ) ) {
$data = $block['attrs']['data'][ $field['name'] ];
}
}
}
}
}
else if (
$info['type'] === 'post' ||
$info['type'] === 'woo_order'
) {
$data = get_post_meta( $info['id'], $field['name'], true );
}
else if ( $info['type'] === 'user' ) {
$data = get_user_meta( $info['id'], $field['name'], true );
}
else if ( $info['type'] === 'term' ) {
$data = get_term_meta( $info['id'], $field['name'], true );
}
else if ( $info['type'] === 'option' ) {
$data = get_option('options_' . $field['name']);
}
}
if ( is_string( $data ) ) {
$data = json_decode( $data, true );
}
if ( ! is_array( $data ) ) {
$data = array();
}
$data_restore = $data;
// }
// GET SOME REQUIRED VARS {
$db_amount_of_top_cols = 1;
$new_amount_of_body_cols = $db_amount_of_top_cols;
if ( isset( $data['c'] ) ) {
$db_amount_of_top_cols = count( $data['c'] );
}
if ( isset( $value['b'][0] ) ) {
$new_amount_of_body_cols = count( $value['b'][0] );
}
if ( isset( $value['body'][0] ) ) {
$new_amount_of_body_cols = count( $value['body'][0] );
}
// }
// UPDATES FIELD DATA BY UNFORMATED VALUE {
if ( isset( $value['p']['o']['uh'] ) ) {
$data['p']['o']['uh'] = $value['p']['o']['uh'] ;
}
if ( isset( $value['p']['ca'] ) ) {
$data['p']['ca'] = $value['p']['ca'] ;
}
if (
isset( $value['h'] ) AND
$value['h'] !== false
) {
$data['h'] = $value['h'];
}
if ( isset( $value['b'] ) ) {
$data['b'] = $value['b'];
}
// }
// UPDATES FIELD DATA BY FORMATED VALUE {
if (
isset( $value['use_header'] ) &&
$value['use_header'] === true
) {
$data['p']['o']['uh'] = 1;
}
if (
isset( $value['use_header'] )
&& $value['use_header'] === false
) {
$data['p']['o']['uh'] = 0;
}
if ( isset( $value['caption'] ) ) {
$data['p']['ca'] = $value['caption'];
}
if (
isset( $value['header'] ) AND
$value['header'] !== false
) {
$data['h'] = $value['header'];
}
if ( isset( $value['body'] ) ) {
$data['b'] = $value['body'];
}
// }
// APPLIES CHANGES TO VALUE {
$value = $data;
// }
// SYNCHRONICE TOP ROW DATA WITH CHANGED AMOUNT OF BODY COLUMNS {
if ( $new_amount_of_body_cols > $db_amount_of_top_cols ) {
for ( $i = $db_amount_of_top_cols; $i < $new_amount_of_body_cols; $i++ ) {
// adds a column entry in top row data
$value['c'] = $value['c'] ?? array();
array_push( $value['c'], array( 'p' => '' ) );
}
}
if ( $new_amount_of_body_cols < $db_amount_of_top_cols ) {
for ( $i = $new_amount_of_body_cols; $i < $db_amount_of_top_cols; $i++ ) {
// removes a column entry in top row data
array_pop( $value['c'] );
}
}
// }
// UPDATES PLUGIN VERSION {
$value['acftf']['v'] = ACF_TABLE_FIELD_PLUGIN_VERSION;
// }
// ENSURES ORDERING ROWS AND COLS BY GIVEN KEYS {
// ensures ordering of header row data
if ( isset( $value['h'] ) ) {
ksort( $value['h'], SORT_NUMERIC );
}
// ensures ordering of body rows and cols
if ( isset( $value['b'] ) ) {
ksort( $value['b'], SORT_NUMERIC );
foreach ( $value['b'] as &$row_data ) {
ksort( $row_data, SORT_NUMERIC );
}
unset( $row_data );
}
// }
// APPLIES DEFAULT VALUES BY SCHEMA {
$schema = $this->get_rest_schema( $field );
$value = $this->apply_schema_defaults( $value, $schema );
// }
// STORES REST VALIDATION RESULT {
$validation_before_sanitize = rest_validate_value_from_schema( $value, $schema );
// }
// SANITIZES VALUE BY REST SCHEMA {
$value = rest_sanitize_value_from_schema( $value, $schema );
// }
// SANITIZES STRING VALUES {
array_walk_recursive( $value, function ( &$item ) {
if ( is_string( $item ) ) {
$item = wp_kses_post( $item );
}
});
// }
// VALIDATES VALUE BY REST SCHEMA {
$sanitized_validation = rest_validate_value_from_schema( $value, $schema );
if ( is_wp_error( $sanitized_validation ) ) {
if ( is_wp_error( $validation_before_sanitize ) ) {
error_log( print_r( $validation_before_sanitize, true ) );
}
else {
error_log( print_r( $sanitized_validation, true ) );
}
return $data_restore;
}
// }
// $post_id is integer when post is saved, $post_id is string when block is saved
/* if ( gettype( $post_id ) === 'integer' ) {
// only saving a post needs addslashes
$value = $this->table_slash( $value );
} */
return $value;
}
/*
* 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 ) {
if ( is_string( $value ) ) {
// CHECK FOR GUTENBERG BLOCK CONTENT (URL ENCODED JSON) {
if ( substr( $value , 0 , 1 ) === '%' ) {
$value = urldecode( $value );
}
// }
$value = json_decode( $value, true ); // decode gutenberg JSONs, but also old table JSONs strings to array
}
$a = $value;
$value = false;
// IF BODY DATA
if (
! empty( $a['b'] ) AND
is_countable( $a['b'] ) AND
count( $a['b'] ) > 0
) {
$value = array();
// IF HEADER DATA
if ( isset( $a['p']['o']['uh'] ) ) {
if ( 0 === $a['p']['o']['uh'] ) {
$value['use_header'] = false;
}
elseif ( 1 === $a['p']['o']['uh'] ) {
$value['use_header'] = true;
}
}
if ( ! empty( $a['p']['o']['uh'] ) ) {
$value['header'] = $a['h'];
}
else {
$value['header'] = false;
}
// IF CAPTION DATA
if (
! empty( $field['use_caption'] ) AND
$field['use_caption'] === 1 AND
! empty( $a['p']['ca'] )
) {
$value['caption'] = $a['p']['ca'];
}
else {
$value['caption'] = false;
}
// BODY
$value['body'] = $a['b'];
// IF SINGLE EMPTY CELL, THEN DO NOT RETURN TABLE DATA
if (
count( $a['b'] ) === 1
AND count( $a['b'][0] ) === 1
AND trim( $a['b'][0][0]['c'] ) === ''
) {
$value = false;
}
}
return $value;
}
/*
* validate_value()
*
* This filter is used to perform validation on the value prior to saving.
* All values are validated regardless of the field's required setting. This allows you to validate and return
* messages to the user if the value is not correct
*
* @type filter
* @date 11/02/2014
* @since 5.0.0
*
* @param $valid (boolean) validation status based on the value and the field's required setting
* @param $value (mixed) the $_POST value
* @param $field (array) the field array holding all the field options
* @param $input (string) the corresponding input name for $_POST value
* @return $valid
*/
/*
function validate_value( $valid, $value, $field, $input ){
// Basic usage
if( $value < $field['custom_minimum_setting'] )
{
$valid = false;
}
// Advanced usage
if( $value < $field['custom_minimum_setting'] )
{
$valid = __('The value is too little!','advanced-custom-fields-table-field'),
}
// return
return $valid;
}
*/
/*
* delete_value()
*
* This action is fired after a value has been deleted from the db.
* Please note that saving a blank value is treated as an update, not a delete
*
* @type action
* @date 6/03/2014
* @since 5.0.0
*
* @param $post_id (mixed) the $post_id from which the value was deleted
* @param $key (string) the $meta_key which the value was deleted
* @return n/a
*/
/*
function delete_value( $post_id, $key ) {
}
*/
/*
* load_field()
*
* This filter is applied to the $field after it is loaded from the database
*
* @type filter
* @date 23/01/2013
* @since 3.6.0
*
* @param $field (array) the field array holding all the field options
* @return $field
*/
/*
function load_field( $field ) {
return $field;
}
*/
/*
* update_field()
*
* This filter is applied to the $field before it is saved to the database
*
* @type filter
* @date 23/01/2013
* @since 3.6.0
*
* @param $field (array) the field array holding all the field options
* @return $field
*/
/*
function update_field( $field ) {
return $field;
}
*/
/*
* delete_field()
*
* This action is fired after a field is deleted from the database
*
* @type action
* @date 11/02/2014
* @since 5.0.0
*
* @param $field (array) the field array holding all the field options
* @return n/a
*/
/*
function delete_field( $field ) {
}
*/
/**
* get_rest_schema()
*
* Provides the rest api scheme.
*/
public function get_rest_schema( array $field ) {
$schema = array(
'type' => array( 'object', 'null' ),
'title' => 'ACF table custom field type',
'properties' => array(
'acftf' => array(
'title' => 'Table general info',
'type' => 'object',
'properties' => array(
'v' => array(
'title' => 'Table Field plugin version',
'type' => 'string',
'required' => true,
'default' => ACF_TABLE_FIELD_PLUGIN_VERSION,
),
),
'additionalProperties' => false,
'required' => true,
'default' => array( 'v' => ACF_TABLE_FIELD_PLUGIN_VERSION ),
),
'p' => array(
'title' => 'Field parameter',
'type' => 'object',
'properties' => array(
'o' => array(
'title' => 'Table options',
'type' => 'object',
'properties' => array(
'uh' => array(
'title' => 'Use table header',
'type' => 'integer',
'enum' => array( 0, 1 ),
'required' => true,
'default' => 0,
),
),
'additionalProperties' => false,
'default' => array( 'uh' => 0 ),
'required' => true,
),
'ca' => array(
'title' => 'Table caption',
'type' => 'string',
'required' => true,
'default' => '',
),
),
'additionalProperties' => false,
'required' => true,
'default' => array( 'o' => array( 'uh' => 0 ), 'ca' => '' ),
),
'c' => array(
'title' => 'Table columns',
'type' => 'array',
'items' => array(
'title' => 'Table column',
'type' => 'object',
'properties' => array(
'p' => array(
'title' => 'Column parameters',
'type' => 'object',
'required' => true,
'default' => array(),
),
),
'additionalProperties' => false,
'default' => array( 'p' => array() ),
'required' => true,
),
'required' => true,
'default' => array( array( 'p' => '' ) ),
),
'h' => array(
'title' => 'Table header rows',
'type' => 'array',
'items' => array(
'title' => 'Table header cell',
'type' => 'object',
'properties' => array(
'c' => array(
'title' => 'Header cell content',
'type' => 'string',
'required' => true,
'default' => '',
),
),
'additionalProperties' => false,
'default' => array( 'c' => '' ),
),
'required' => true,
'default' => array( array( 'c' => '' ) ),
),
'b' => array(
'title' => 'Table body rows',
'type' => 'array',
'items' => array(
'title' => 'Table body columns',
'type' => 'array',
'items' => array(
'title' => 'Table body cell',
'type' => 'object',
'properties' => array(
'c' => array(
'title' => 'Header row cells',
'type' => 'string',
'required' => true,
'default' => '',
),
),
'additionalProperties' => false,
'default' => array( 'c' => '' ),
),
'required' => true,
'default' => array( array( 'c' => '' ) ),
),
'required' => true,
'default' => array( array( array( 'c' => '' ) ) ),
),
),
'additionalProperties' => false,
'required' => ! empty( $field['required'] ) ? true : false,
);
return $schema;
}
/**
* apply_schema_defaults()
*
* Applies default value entries given by schema
*/
function apply_schema_defaults( $value, array $schema ) {
if (
( null === $value || array() === $value ) &&
isset( $schema['default'] )
) {
$value = $schema['default'];
}
$schema_types = array();
if ( is_array( $schema['type'] ) ) {
$schema_types = $schema['type'];
}
if ( is_string( $schema['type'] ) ) {
$schema_types[] = $schema['type'];
}
if (
in_array( 'object', $schema_types ) &&
isset( $schema['properties'] ) &&
is_array( $value )
) {
foreach ( $schema['properties'] as $key => $prop_schema ) {
$value[ $key ] = $this->apply_schema_defaults( $value[ $key ] ?? null, $prop_schema );
}
}
if (
'array' === $schema['type'] &&
isset( $schema['items'] ) &&
is_array( $value )
) {
foreach ( $value as $i => $item ) {
$value[ $i ] = $this->apply_schema_defaults( $item, $schema['items'] );
}
}
return $value;
}
/**
* table_slash()
*
* Add slashes to a string or strings in an array.
*
* This should be used instead of wp_slash() because wp_slash() convertes all
* array values to strings which affects also the table object values of
* type number converting to string.
*/
function table_slash( $value ) {
if ( is_array( $value ) ) {
foreach ( $value as $k => $v ) {
if (
is_array( $v ) OR
is_object( $v )
) {
$value[ $k ] = $this->table_slash( $v );
}
else if( is_string( $v ) ) {
$value[ $k ] = addslashes( $v );
}
else {
$value[ $k ] = $v;
}
}
} else {
if ( null !== $value ) {
$value = addslashes( $value );
}
}
return $value;
}
}
}