Current File : /home/pacjaorg/wpt.pacja.org/wp-content/plugins/formidable/classes/models/FrmField.php |
<?php
if ( ! defined( 'ABSPATH' ) ) {
die( 'You are not allowed to call this page directly.' );
}
class FrmField {
public static $use_cache = true;
public static $transient_size = 200;
public static function field_selection() {
$fields = array(
'text' => array(
'name' => __( 'Text', 'formidable' ),
'icon' => 'frm_icon_font frm_text2_icon',
),
'textarea' => array(
'name' => __( 'Paragraph', 'formidable' ),
'icon' => 'frm_icon_font frm_paragraph_icon',
),
'checkbox' => array(
'name' => __( 'Checkboxes', 'formidable' ),
'icon' => 'frm_icon_font frm_check_square_icon',
),
'radio' => array(
'name' => __( 'Radio Buttons', 'formidable' ),
'icon' => 'frm_icon_font frm_radio_checked_icon',
),
'select' => array(
'name' => __( 'Dropdown', 'formidable' ),
'icon' => 'frm_icon_font frm_caret_square_down_icon',
),
'email' => array(
'name' => __( 'Email', 'formidable' ),
'icon' => 'frm_icon_font frm_email_icon',
),
'url' => array(
'name' => __( 'Website/URL', 'formidable' ),
'icon' => 'frm_icon_font frm_link_icon',
),
'number' => array(
'name' => __( 'Number', 'formidable' ),
'icon' => 'frm_icon_font frm_hashtag_icon',
),
'name' => array(
'name' => __( 'Name', 'formidable' ),
'icon' => 'frm_icon_font frm_user_name_icon',
),
'phone' => array(
'name' => __( 'Phone', 'formidable' ),
'icon' => 'frm_icon_font frm_phone_icon',
),
'html' => array(
'name' => __( 'HTML', 'formidable' ),
'icon' => 'frm_icon_font frm_code_icon',
),
'hidden' => array(
'name' => __( 'Hidden', 'formidable' ),
'icon' => 'frm_icon_font frm_eye_slash_icon',
),
'user_id' => array(
'name' => __( 'User ID', 'formidable' ),
'icon' => 'frm_icon_font frm_user_icon',
),
'captcha' => array(
'name' => self::get_captcha_field_name(),
'icon' => 'frm_icon_font frm_shield_check_icon',
),
'credit_card' => array(
'name' => __( 'Payment', 'formidable' ),
'icon' => 'frm_icon_font frm_credit_card_icon',
),
FrmSubmitHelper::FIELD_TYPE => array(
'name' => __( 'Submit', 'formidable' ),
'hide' => true,
),
);
/**
* @param array $fields
*/
return apply_filters( 'frm_available_fields', $fields );
}
/**
* Get the name of the Captcha field based on the global Captcha setting.
*
* @return string
*/
private static function get_captcha_field_name() {
return 'Captcha';
}
public static function pro_field_selection() {
$images_url = FrmAppHelper::plugin_url() . '/images/';
$fields = array(
'file' => array(
'name' => __( 'File Upload', 'formidable' ),
'icon' => 'frm_icon_font frm_upload_icon',
'message' => __( 'Add file uploads to save time and cut down on back-and-forth. Upgrade to Pro to get Upload fields and more.', 'formidable' ),
),
'ranking' => array(),
'rte' => array(
'name' => __( 'Rich Text', 'formidable' ),
'icon' => 'frm_icon_font frm_align_right_icon',
),
'date' => array(
'name' => __( 'Date', 'formidable' ),
'icon' => 'frm_icon_font frm_calendar_icon',
),
'time' => array(
'name' => __( 'Time', 'formidable' ),
'icon' => 'frm_icon_font frm_clock_icon',
),
'scale' => array(
'name' => __( 'Scale', 'formidable' ),
'icon' => 'frm_icon_font frm_linear_scale_icon',
'message' => esc_html__( 'Add a set of radio buttons with whatever range you choose.', 'formidable' ) . '<img src="' . esc_url( $images_url ) . 'scale_field.png" alt="' . esc_attr__( 'Scale Field', 'formidable' ) . '" />',
),
'star' => array(
'name' => __( 'Star Rating', 'formidable' ),
'icon' => 'frm_icon_font frm_star_icon',
),
'range' => array(
'name' => __( 'Slider', 'formidable' ),
'icon' => 'frm_icon_font frm_code_commit_icon',
),
'toggle' => array(
'name' => __( 'Toggle', 'formidable' ),
'icon' => 'frm_icon_font frm_toggle_on_icon',
),
'data' => array(
'name' => __( 'Dynamic', 'formidable' ),
'icon' => 'frm_icon_font frm_sitemap_icon',
'message' => __( 'Create relationships between multiple forms. You can link a member to a team, a rating to a product, a comment to a submission, and much more.', 'formidable' ),
),
'lookup' => array(
'name' => __( 'Lookup', 'formidable' ),
'icon' => 'frm_icon_font frm_search_icon',
'message' => esc_html__( 'Filter the options in the next field and automatically add values to other fields. Upgrade to Pro to get Lookup fields and more.', 'formidable' ) . ' <img src="' . esc_url( $images_url ) . 'look-up_year-make-model.gif" alt="' . esc_attr__( 'cascading lookup fields', 'formidable' ) . '" />',
),
'divider|repeat' => array(
'name' => __( 'Repeater', 'formidable' ),
'icon' => 'frm_icon_font frm_repeater_icon',
'message' => esc_html__( 'Allow your visitors to add new sets of fields while filling out forms. Increase conversions while saving building time and server resources.', 'formidable' ) . ' <img src="' . esc_url( $images_url ) . 'repeatable-section_frontend.gif" alt="' . esc_attr__( 'Dynamically Add Form Fields with repeatable sections', 'formidable' ) . '" />',
),
'end_divider' => array(
'name' => __( 'Section Buttons', 'formidable' ),
'switch_from' => 'divider',
),
'divider' => array(
'name' => __( 'Section', 'formidable' ),
'icon' => 'frm_icon_font frm_header_icon',
),
'break' => array(
'name' => __( 'Page Break', 'formidable' ),
'icon' => 'frm_icon_font frm_page_break_icon',
'message' => __( 'Get multi-paged forms with progress bars. Did you know you can upgrade to PRO to unlock multi-step forms with more awesome features?', 'formidable' ),
),
'form' => array(
'name' => __( 'Embed Form', 'formidable' ),
'icon' => 'frm_icon_font frm_file_text_icon',
),
'likert' => array(
'name' => __( 'Likert Scale', 'formidable' ),
'icon' => 'frm_icon_font frm_likert_scale frm_show_upgrade',
'addon' => 'surveys',
),
'nps' => array(
'name' => __( 'NPS', 'formidable' ),
'icon' => 'frm_icon_font frm_nps frm_show_upgrade',
'addon' => 'surveys',
),
'password' => array(
'name' => __( 'Password', 'formidable' ),
'icon' => 'frm_icon_font frm_lock_open_icon',
),
'tag' => array(
'name' => __( 'Tags', 'formidable' ),
'icon' => 'frm_icon_font frm_price_tags_icon',
),
// This is no longer a Pro field, but without this here, Pro triggers "undefined index" notices.
// Right now it leaves a gap. Maybe we can skip anything without a name or something.
'credit_card' => array(
'name' => '',
'icon' => '',
),
'address' => array(
'name' => __( 'Address', 'formidable' ),
'icon' => 'frm_icon_font frm_location_icon',
),
'summary' => array(
'name' => __( 'Summary', 'formidable' ),
'icon' => 'frm_icon_font frm_file_text_icon',
'message' => __( 'Allow visitors to review their responses before a form is submitted. Upgrade to Pro to get Summary fields and more.', 'formidable' ),
),
'signature' => array(
'name' => __( 'Signature', 'formidable' ),
'icon' => 'frm_icon_font frm_signature_icon frm_show_upgrade',
'addon' => 'signature',
),
'ai' => array(
'name' => __( 'AI', 'formidable' ),
'icon' => 'frm_icon_font frm_eye_icon frm_show_upgrade',
'addon' => 'ai',
'message' => __( 'Streamline workflows and reclaim valuable time with the power of AI. You can effortlessly respond to your visitors in real-time with ChatGPT as your automated assistant. Upgrade to Pro and unlock AI-powered fields.', 'formidable' ),
),
'ssa-appointment' => array(
'name' => __( 'Appointment', 'formidable' ),
'icon' => 'frm_icon_font frm_calendar_icon frm_show_upgrade',
'require' => 'Simply Schedule Appointments',
'message' => sprintf(
/* translators: %1$s: Link opening HTML, %2$s: Link tag closing */
esc_html__( 'Appointment fields are an integration with %1$sSimply Schedule Appointments%2$s. Get started now to schedule appointments directly from your forms.', 'formidable' ),
'<a href="https://simplyscheduleappointments.com/meet/formidable/">',
'</a>'
) . '<img src="' . esc_url( $images_url ) . 'appointments.png" alt="' . esc_attr__( 'Scheduling', 'formidable' ) . '" />',
'link' => 'https://simplyscheduleappointments.com/meet/formidable/',
),
'product' => array(
'name' => __( 'Product', 'formidable' ),
'icon' => 'frm_icon_font frm_product_icon',
'section' => 'pricing',
),
'quantity' => array(
'name' => __( 'Quantity', 'formidable' ),
'icon' => 'frm_icon_font frm_quantity_icon',
'section' => 'pricing',
),
'total' => array(
'name' => __( 'Total', 'formidable' ),
'icon' => 'frm_icon_font frm_total_icon',
'section' => 'pricing',
),
);
if ( self::include_ranking_fields() ) {
$fields['ranking'] = array(
'name' => __( 'Ranking', 'formidable' ),
'icon' => 'frm_icon_font frm_chart_bar_icon frm_show_upgrade',
'message' => __( 'Now you can effortlessly gather insights, preferences, and opinions by allowing users to rank options.', 'formidable' ),
'upsell_image' => esc_url( $images_url ) . 'ranking-field.svg',
'addon' => 'surveys',
'is_new' => self::field_is_new( 'ranking' ),
);
} else {
unset( $fields['ranking'] );
}
if ( ! FrmAppHelper::show_new_feature( 'ai' ) ) {
unset( $fields['ai'] );
}
// Since the signature field may be in a different section, don't show it twice.
$lite_fields = self::field_selection();
if ( isset( $lite_fields['signature'] ) ) {
unset( $fields['signature'] );
}
return apply_filters( 'frm_pro_available_fields', $fields );
}
/**
* Check if we should show ranking fields in the builder.
* This is based on the active version coming from our API data.
* If Surveys v1.1 is not released yet, we don't want to display ranking fields yet.
*
* @since 6.8.3
*
* @return bool
*/
private static function include_ranking_fields() {
if ( class_exists( 'FrmSurveys\models\fields\Ranking' ) ) {
// Always return true if Ranking fields exist.
return true;
}
$plugin = 'formidable-surveys/formidable-surveys.php';
$expected_version = '1.1';
return self::installed_plugin_meets_version( $plugin, $expected_version ) || self::api_meets_version( $plugin, $expected_version );
}
/**
* @since 6.8.3
*
* @param string $plugin
* @param string $expected_version
* @return bool
*/
private static function installed_plugin_meets_version( $plugin, $expected_version ) {
$installed_version = self::get_installed_version( $plugin );
return $installed_version && version_compare( $installed_version, $expected_version, '>=' );
}
/**
* @since 6.8.3
*
* @param string $plugin
* @return false|string String version. False if the plugin is not installed.
*/
private static function get_installed_version( $plugin ) {
if ( ! function_exists( 'get_plugins' ) ) {
require_once ABSPATH . 'wp-admin/includes/plugin.php';
}
$plugins = get_plugins();
if ( isset( $plugins[ $plugin ] ) && ! empty( $plugins[ $plugin ]['Version'] ) ) {
return $plugins[ $plugin ]['Version'];
}
return false;
}
/**
* @since 6.8.3
*
* @param string $plugin
* @param string $expected_version
* @return bool
*/
private static function api_meets_version( $plugin, $expected_version ) {
$api = new FrmFormApi();
$addons = $api->get_api_info();
$matches = wp_list_filter( $addons, array( 'plugin' => $plugin ) );
if ( ! $matches ) {
return false;
}
$match = reset( $matches );
if ( empty( $match['new_version'] ) ) {
return false;
}
$api_version = $match['new_version'];
return version_compare( $api_version, $expected_version, '>=' );
}
/**
* Consider a field new for 90 days after the release date.
*
* @since 6.8.3
*
* @param string $type
* @return bool
*/
private static function field_is_new( $type ) {
if ( 'ranking' === $type ) {
$ranking_release_date = '2024-03-12';
$three_months_after_release = gmdate( 'Y-m-d', strtotime( $ranking_release_date . ' + 90 days' ) );
return gmdate( 'Y-m-d' ) < $three_months_after_release;
}
return false;
}
/**
* @since 4.0
*
* @return array
*/
public static function all_field_selection() {
$pro_field_selection = self::pro_field_selection();
return array_merge( $pro_field_selection, self::field_selection() );
}
/**
* Create a field.
*
* @param array $values
* @param bool $return
* @return false|int
*/
public static function create( $values, $return = true ) {
global $wpdb, $frm_duplicate_ids;
$new_values = array();
$key = isset( $values['field_key'] ) ? $values['field_key'] : $values['name'];
$new_values['field_key'] = FrmAppHelper::get_unique_key( $key, $wpdb->prefix . 'frm_fields', 'field_key' );
$values = FrmAppHelper::maybe_filter_array( $values, array( 'name', 'description' ) );
foreach ( array( 'name', 'description', 'type', 'default_value' ) as $col ) {
$new_values[ $col ] = $values[ $col ];
}
$new_values['options'] = self::maybe_filter_options( $values['options'] );
$new_values['field_order'] = isset( $values['field_order'] ) ? (int) $values['field_order'] : null;
$new_values['required'] = isset( $values['required'] ) ? (int) $values['required'] : 0;
$new_values['form_id'] = isset( $values['form_id'] ) ? (int) $values['form_id'] : null;
$new_values['field_options'] = $values['field_options'];
$new_values['created_at'] = current_time( 'mysql', 1 );
if ( isset( $values['id'] ) ) {
$frm_duplicate_ids[ $values['field_key'] ] = $new_values['field_key'];
$new_values = apply_filters( 'frm_duplicated_field', $new_values );
}
self::preserve_format_option_backslashes( $new_values );
foreach ( $new_values as $k => $v ) {
if ( is_array( $v ) ) {
if ( $k === 'default_value' ) {
$new_values[ $k ] = FrmAppHelper::maybe_json_encode( $v );
} else {
$new_values[ $k ] = serialize( $v );
}
}
unset( $k, $v );
}
$query_results = $wpdb->insert( $wpdb->prefix . 'frm_fields', $new_values );
if ( ! $query_results ) {
return false;
}
self::delete_form_transient( $new_values['form_id'] );
$new_id = $wpdb->insert_id;
if ( ! $return ) {
return false;
}
if ( isset( $values['id'] ) ) {
$frm_duplicate_ids[ $values['id'] ] = $new_id;
}
return $new_id;
}
/**
* Maybe filter HTML in field options data.
* HTML is only filtered when unsafe HTML is disallowed.
* See FrmAppHelper::allow_unfiltered_html.
*
* @since 5.0.08
*
* @param array $options
* @return array
*/
private static function maybe_filter_options( $options ) {
$options = FrmAppHelper::maybe_filter_array( $options, array( 'custom_html' ) );
if ( ! empty( $options['custom_html'] ) ) {
$options['custom_html'] = self::maybe_filter_custom_html_input_attributes( $options['custom_html'] );
}
if ( ! empty( $options['classes'] ) ) {
$options['classes'] = implode( ' ', array_map( 'FrmFormsHelper::sanitize_layout_class', explode( ' ', $options['classes'] ) ) );
}
return $options;
}
/**
* Prevent users who do not have permission to insert JavaScript attributes in input elements.
* This is triggered when a field is updated.
*
* @since 6.11.2
*
* @param string $html
* @return string
*/
private static function maybe_filter_custom_html_input_attributes( $html ) {
if ( FrmAppHelper::allow_unfiltered_html() ) {
return $html;
}
$pattern = get_shortcode_regex( array( 'input' ) );
return preg_replace_callback(
"/$pattern/",
/**
* @param array $match Shortcode data.
* @return string
*/
function ( $match ) {
$attr = shortcode_parse_atts( $match[3] );
if ( ! is_array( $attr ) ) {
// In old versions of WordPress (older than 6.5), this might not be an array.
return '[input]';
}
$safe_atts = array();
foreach ( $attr as $attr_key => $att ) {
if ( ! is_numeric( $attr_key ) ) {
// opt=1 without parentheses for example is mapped like 'opt' => 1.
$key = $attr_key;
$value = $att;
} else {
// Some data is mapped like 0 => 'placeholder="Placeholder"'.
$split = explode( '=', $att, 2 );
if ( 2 !== count( $split ) ) {
continue;
}
$key = trim( $split[0] );
$value = trim( $split[1], '"' );
}
if ( FrmAppHelper::input_key_is_safe( $key, 'update' ) ) {
$safe_atts[ $key ] = $value;
}
}
if ( ! $safe_atts ) {
return '[input]';
}
return '[input ' . FrmAppHelper::array_to_html_params( $safe_atts ) . ']';
},
$html
);
}
/**
* Process the field duplication.
*
* @since 5.0.05
*/
public static function duplicate_single_field( $field_id, $form_id ) {
$copy_field = self::getOne( $field_id );
if ( ! $copy_field ) {
return false;
}
do_action( 'frm_duplicate_field', $copy_field, $form_id );
do_action( 'frm_duplicate_field_' . $copy_field->type, $copy_field, $form_id );
$values = array(
'id' => $copy_field->id,
);
FrmFieldsHelper::fill_field( $values, $copy_field, $copy_field->form_id );
$values = apply_filters( 'frm_prepare_single_field_for_duplication', $values );
$field_id = self::create( $values );
/**
* Fires after duplicating a field.
*
* @since 5.0.04
*
* @param array $args {
* The arguments.
*
* @type int $field_id New field ID.
* @type array $values Values before inserting.
* @type object $copy_field Copy field data.
* @type int $form_id Form ID.
* }
*/
do_action( 'frm_after_duplicate_field', compact( 'field_id', 'values', 'copy_field', 'form_id' ) );
return compact( 'field_id', 'values' );
}
public static function duplicate( $old_form_id, $form_id, $copy_keys = false, $blog_id = false ) {
global $frm_duplicate_ids;
$where = array(
array(
'or' => 1,
'fi.form_id' => $old_form_id,
'fr.parent_form_id' => $old_form_id,
),
);
$fields = self::getAll( $where, 'field_order', '', $blog_id );
foreach ( (array) $fields as $field ) {
$new_key = $copy_keys ? $field->field_key : '';
if ( $copy_keys && substr( $field->field_key, - 1 ) == 2 ) {
$new_key = rtrim( $new_key, 2 );
}
$values = array();
FrmFieldsHelper::fill_field( $values, $field, $form_id, $new_key );
// If this is a repeating section, create new form
if ( self::is_repeating_field( $field ) ) {
// create the repeatable form
$new_repeat_form_id = apply_filters(
'frm_create_repeat_form',
0,
array(
'parent_form_id' => $form_id,
'field_name' => $field->name,
)
);
// Save old form_select
$old_repeat_form_id = $field->field_options['form_select'];
// Update form_select for repeating field
$values['field_options']['form_select'] = $new_repeat_form_id;
}
// If this is a field inside of a repeating section, associate it with the correct form
if ( $field->form_id != $old_form_id && isset( $old_repeat_form_id ) && isset( $new_repeat_form_id ) && $field->form_id == $old_repeat_form_id ) {
$values['form_id'] = $new_repeat_form_id;
}
$values['description'] = FrmFieldsHelper::switch_field_ids( $values['description'] );
$values = apply_filters( 'frm_duplicated_field', $values );
$new_id = self::create( $values );
$frm_duplicate_ids[ $field->id ] = $new_id;
$frm_duplicate_ids[ $field->field_key ] = $new_id;
unset( $field );
}//end foreach
}
/**
* @param int|string $id
* @param array $values
* @return false|int
*/
public static function update( $id, $values ) {
global $wpdb;
$id = absint( $id );
$values = FrmAppHelper::maybe_filter_array( $values, array( 'name', 'description' ) );
if ( isset( $values['field_key'] ) ) {
$values['field_key'] = FrmAppHelper::get_unique_key( $values['field_key'], $wpdb->prefix . 'frm_fields', 'field_key', $id );
}
if ( isset( $values['required'] ) ) {
$values['required'] = (int) $values['required'];
}
self::preserve_format_option_backslashes( $values );
if ( isset( $values['type'] ) ) {
if ( 'dropdown' === $values['type'] ) {
// To avoid conflicts with security plugins the value "dropdown" is sent for select fields.
// This is because "select" gets matched for SQL injection attempts.
$values['type'] = 'select';
}
/**
* @since 6.9 The Field ID param was added.
*
* @param array $values
* @param int $id Field ID.
*/
$values = apply_filters( 'frm_clean_' . $values['type'] . '_field_options_before_update', $values, $id );
if ( $values['type'] === 'hidden' && isset( $values['field_options'] ) && isset( $values['field_options']['clear_on_focus'] ) ) {
// don't keep the old placeholder setting for hidden fields
$values['field_options']['clear_on_focus'] = 0;
}
}
// serialize array values
foreach ( array( 'field_options', 'options' ) as $opt ) {
if ( isset( $values[ $opt ] ) && is_array( $values[ $opt ] ) ) {
if ( 'field_options' === $opt ) {
$values[ $opt ] = self::maybe_filter_options( $values[ $opt ] );
}
$values[ $opt ] = serialize( $values[ $opt ] );
}
}
if ( isset( $values['default_value'] ) && is_array( $values['default_value'] ) ) {
$values['default_value'] = json_encode( $values['default_value'] );
}
$query_results = $wpdb->update( $wpdb->prefix . 'frm_fields', $values, array( 'id' => $id ) );
$form_id = 0;
if ( isset( $values['form_id'] ) ) {
$form_id = absint( $values['form_id'] );
} else {
$field = self::getOne( $id );
if ( $field ) {
$form_id = $field->form_id;
}
unset( $field );
}
unset( $values );
if ( $query_results ) {
wp_cache_delete( $id, 'frm_field' );
if ( $form_id ) {
self::delete_form_transient( $form_id );
}
}
return $query_results;
}
/**
* Keep backslashes in the phone format option
*
* @since 2.0.8
*
* @param array $values Pass by reference.
*/
private static function preserve_format_option_backslashes( &$values ) {
if ( isset( $values['field_options']['format'] ) ) {
$values['field_options']['format'] = FrmAppHelper::preserve_backslashes( $values['field_options']['format'] );
}
}
public static function destroy( $id ) {
global $wpdb;
do_action( 'frm_before_destroy_field', $id );
wp_cache_delete( $id, 'frm_field' );
$field = self::getOne( $id );
if ( ! $field ) {
return false;
}
self::delete_form_transient( $field->form_id );
$wpdb->query( $wpdb->prepare( 'DELETE FROM ' . $wpdb->prefix . 'frm_item_metas WHERE field_id=%d', $id ) );
return $wpdb->query( $wpdb->prepare( 'DELETE FROM ' . $wpdb->prefix . 'frm_fields WHERE id=%d', $id ) );
}
public static function delete_form_transient( $form_id ) {
$form_id = absint( $form_id );
delete_transient( 'frm_form_fields_' . $form_id . 'excludeinclude' );
delete_transient( 'frm_form_fields_' . $form_id . 'includeinclude' );
delete_transient( 'frm_form_fields_' . $form_id . 'includeexclude' );
delete_transient( 'frm_form_fields_' . $form_id . 'excludeexclude' );
global $wpdb;
$wpdb->query( $wpdb->prepare( 'DELETE FROM ' . $wpdb->options . ' WHERE option_name LIKE %s OR option_name LIKE %s OR option_name LIKE %s OR option_name LIKE %s', '_transient_timeout_frm_form_fields_' . $form_id . 'ex%', '_transient_frm_form_fields_' . $form_id . 'ex%', '_transient_timeout_frm_form_fields_' . $form_id . 'in%', '_transient_frm_form_fields_' . $form_id . 'in%' ) );
FrmDb::cache_delete_group( 'frm_field' );
$form = FrmForm::getOne( $form_id );
if ( $form && $form->parent_form_id && $form->parent_form_id != $form_id ) {
self::delete_form_transient( $form->parent_form_id );
}
}
/**
* If $field is numeric, get the field object
*
* @param int|object|string $field
* @return void
*/
public static function maybe_get_field( &$field ) {
if ( ! is_object( $field ) ) {
$field = self::getOne( $field );
}
}
/**
* @param int|string $id The field id or key.
* @param bool $filter When true, run the frm_field filter.
*/
public static function getOne( $id, $filter = false ) {
if ( empty( $id ) ) {
return null;
}
global $wpdb;
$where = is_numeric( $id ) ? 'id=%d' : 'field_key=%s';
$query = $wpdb->prepare( 'SELECT * FROM ' . $wpdb->prefix . 'frm_fields WHERE ' . $where, $id ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
$results = FrmDb::check_cache( $id, 'frm_field', $query, 'get_row', 0 );
if ( empty( $results ) ) {
self::filter_field( $filter, $results );
return $results;
}
if ( is_numeric( $id ) ) {
FrmDb::set_cache( $results->field_key, $results, 'frm_field' );
} elseif ( $results ) {
FrmDb::set_cache( $results->id, $results, 'frm_field' );
}
self::prepare_options( $results );
self::filter_field( $filter, $results );
return wp_unslash( $results );
}
/**
* @since 3.06.01
* @param bool $filter When true, run the frm_field filter.
* @param object $results
*/
private static function filter_field( $filter, &$results ) {
if ( $filter ) {
/**
* @since 3.06.01
*/
$results = apply_filters( 'frm_field', $results );
}
}
/**
* Get the field type by key or id
*
* @param int|string $id The field id or key.
* @param mixed $col The name of the column in the fields database table.
*/
public static function get_type( $id, $col = 'type' ) {
$field = FrmDb::check_cache( $id, 'frm_field' );
if ( $field ) {
$type = $field->{$col};
} else {
$where = array(
'or' => 1,
'id' => $id,
'field_key' => $id,
);
$type = FrmDb::get_var( 'frm_fields', $where, $col );
}
return $type;
}
/**
* @param int|string $form_id
* @param string $type
* @param int|string $limit
* @param string $inc_sub
*/
public static function get_all_types_in_form( $form_id, $type, $limit = '', $inc_sub = 'exclude' ) {
if ( ! $form_id ) {
return array();
}
$results = self::get_fields_from_transients(
$form_id,
array(
'inc_embed' => $inc_sub,
'inc_repeat' => $inc_sub,
)
);
if ( ! empty( $results ) ) {
$fields = array();
$count = 0;
foreach ( $results as $result ) {
if ( $type != $result->type ) {
continue;
}
$fields[ $result->id ] = $result;
++$count;
if ( $limit == 1 ) {
$fields = $result;
break;
}
if ( ! empty( $limit ) && $count >= $limit ) {
break;
}
unset( $result );
}
return wp_unslash( $fields );
}//end if
self::$use_cache = false;
$where = array(
'fi.form_id' => (int) $form_id,
'fi.type' => $type,
);
self::maybe_include_repeating_fields( $inc_sub, $where );
$results = self::getAll( $where, 'field_order', $limit );
self::$use_cache = true;
self::include_sub_fields( $results, $inc_sub, $type, $form_id );
return $results;
}
/**
* @param int|string $form_id
* @param int|string $limit
* @param string $inc_embed
* @param string $inc_repeat
* @return array
*/
public static function get_all_for_form( $form_id, $limit = '', $inc_embed = 'exclude', $inc_repeat = 'include' ) {
if ( ! (int) $form_id ) {
return array();
}
$results = self::get_fields_from_transients( $form_id, compact( 'inc_embed', 'inc_repeat' ) );
if ( ! empty( $results ) ) {
if ( empty( $limit ) ) {
return $results;
}
$fields = array();
$count = 0;
foreach ( $results as $result ) {
++$count;
$fields[ $result->id ] = $result;
if ( ! empty( $limit ) && $count >= $limit ) {
break;
}
}
return $fields;
}
self::$use_cache = false;
$where = array( 'fi.form_id' => absint( $form_id ) );
self::maybe_include_repeating_fields( $inc_repeat, $where );
$results = self::getAll( $where, 'field_order', $limit );
self::$use_cache = true;
self::include_sub_fields( $results, $inc_embed, 'all', $form_id );
if ( empty( $limit ) ) {
self::set_field_transient( $results, $form_id, 0, compact( 'inc_embed', 'inc_repeat' ) );
}
return $results;
}
/**
* If repeating fields should be included, adjust $where accordingly
*
* @param string $inc_repeat
* @param array $where Pass by reference.
*/
private static function maybe_include_repeating_fields( $inc_repeat, &$where ) {
if ( $inc_repeat === 'include' ) {
$form_id = $where['fi.form_id'];
$where[] = array(
'or' => 1,
'fi.form_id' => $form_id,
'fr.parent_form_id' => $form_id,
);
unset( $where['fi.form_id'] );
}
}
public static function include_sub_fields( &$results, $inc_embed, $type = 'all', $form_id = '' ) {
$no_sub_forms = empty( $results ) && $type === 'all';
if ( 'include' != $inc_embed || $no_sub_forms ) {
return;
}
$form_fields = $results;
$should_get_subforms = ( $type !== 'all' && $type !== 'form' && ! empty( $form_id ) );
if ( $should_get_subforms ) {
$form_fields = self::get_all_types_in_form( $form_id, 'form' );
}
$index_offset = 1;
foreach ( $form_fields as $k => $field ) {
if ( 'form' != $field->type || ! isset( $field->field_options['form_select'] ) ) {
continue;
}
if ( $type === 'all' ) {
$sub_fields = self::get_all_for_form( $field->field_options['form_select'] );
} else {
$sub_fields = self::get_all_types_in_form( $field->field_options['form_select'], $type );
}
if ( ! empty( $sub_fields ) ) {
$index = $k + $index_offset;
$index_offset += count( $sub_fields );
array_splice( $results, $index, 0, $sub_fields );
}
unset( $field, $sub_fields );
}
}
public static function getAll( $where = array(), $order_by = '', $limit = '', $blog_id = false ) {
$cache_key = FrmAppHelper::maybe_json_encode( $where ) . $order_by . 'l' . $limit . 'b' . $blog_id;
if ( self::$use_cache ) {
// make sure old cache doesn't get saved as a transient
$results = wp_cache_get( $cache_key, 'frm_field' );
if ( false !== $results ) {
return wp_unslash( $results );
}
}
global $wpdb;
if ( $blog_id && is_multisite() ) {
global $wpmuBaseTablePrefix;
if ( $wpmuBaseTablePrefix ) {
$prefix = $wpmuBaseTablePrefix . $blog_id . '_';
} else {
$prefix = $wpdb->get_blog_prefix( $blog_id );
}
$table_name = $prefix . 'frm_fields';
$form_table_name = $prefix . 'frm_forms';
} else {
$table_name = $wpdb->prefix . 'frm_fields';
$form_table_name = $wpdb->prefix . 'frm_forms';
}
if ( ! empty( $order_by ) && strpos( $order_by, 'ORDER BY' ) === false ) {
$order_by = ' ORDER BY ' . $order_by;
}
$limit = FrmDb::esc_limit( $limit );
$query = "SELECT fi.*, fr.name as form_name FROM {$table_name} fi LEFT OUTER JOIN {$form_table_name} fr ON fi.form_id=fr.id";
$query_type = $limit === ' LIMIT 1' || $limit == 1 ? 'row' : 'results';
if ( is_array( $where ) ) {
$args = array(
'order_by' => $order_by,
'limit' => $limit,
);
$results = FrmDb::get_var( $table_name . ' fi LEFT OUTER JOIN ' . $form_table_name . ' fr ON fi.form_id=fr.id', $where, 'fi.*, fr.name as form_name', $args, '', $query_type );
} else {
// if the query is not an array, then it has already been prepared
$query .= FrmDb::prepend_and_or_where( ' WHERE ', $where ) . $order_by . $limit;
$function_name = $query_type === 'row' ? 'get_row' : 'get_results';
$results = $wpdb->$function_name( $query );
}
unset( $where );
self::format_field_results( $results );
FrmDb::set_cache( $cache_key, $results, 'frm_field' );
return wp_unslash( $results );
}
/**
* @since 2.0.8
*/
private static function format_field_results( &$results ) {
if ( is_array( $results ) ) {
foreach ( $results as $r_key => $result ) {
self::add_slashes_to_format_before_setting_field_cache( $result );
FrmDb::set_cache( $result->id, $result, 'frm_field' );
FrmDb::set_cache( $result->field_key, $result, 'frm_field' );
self::prepare_options( $result );
$results[ $r_key ]->field_options = $result->field_options;
$results[ $r_key ]->options = $result->options;
$results[ $r_key ]->default_value = $result->default_value;
unset( $r_key, $result );
}
} elseif ( $results ) {
FrmDb::set_cache( $results->id, $results, 'frm_field' );
FrmDb::set_cache( $results->field_key, $results, 'frm_field' );
self::prepare_options( $results );
}
}
/**
* When $result->field_options is an array and not a serialized string there is only a single backslash.
* Cached results are unslashed in FrmField::getAll, so we need to make sure that the cached object has an extra backslash.
* Otherwise the backslash is stripped away on load.
*
* @since 6.15
*
* @param stdClass $result
* @return void
*/
private static function add_slashes_to_format_before_setting_field_cache( $result ) {
if ( ! isset( $result->field_options ) || ! is_array( $result->field_options ) || empty( $result->field_options['format'] ) ) {
return;
}
$result->field_options['format'] = addslashes( $result->field_options['format'] );
}
/**
* Unserialize all the serialized field data
*
* @since 2.0
*/
private static function prepare_options( &$results ) {
FrmAppHelper::unserialize_or_decode( $results->field_options );
FrmAppHelper::unserialize_or_decode( $results->options );
// Allow a single box to be checked for the default value.
$before = $results->default_value;
$field_object = FrmFieldFactory::get_field_type( $results->type );
if ( $field_object->should_unserialize_value() ) {
FrmAppHelper::unserialize_or_decode( $results->default_value );
if ( $before === $results->default_value && ! is_array( $before ) && strpos( $before, '["' ) === 0 ) {
$results->default_value = FrmAppHelper::maybe_json_decode( $results->default_value );
}
}
}
/**
* If a form has too many fields, they won't all save into a single transient.
* We'll break them into groups of 200
*
* @since 2.0.1
*/
private static function get_fields_from_transients( $form_id, $args ) {
$fields = array();
self::get_next_transient( $fields, 'frm_form_fields_' . $form_id . $args['inc_embed'] . $args['inc_repeat'] );
return $fields;
}
/**
* Called by get_fields_from_transients
*
* @since 2.0.1
*/
private static function get_next_transient( &$fields, $base_name, $next = 0 ) {
$name = $next ? $base_name . $next : $base_name;
$next_fields = get_transient( $name );
if ( $next_fields ) {
$fields = array_merge( $fields, $next_fields );
if ( count( $next_fields ) >= self::$transient_size ) {
// if this transient is full, check for another
++$next;
self::get_next_transient( $fields, $base_name, $next );
}
}
}
/**
* Save the transients in chunks for large forms
*
* @since 2.0.1
*/
private static function set_field_transient( &$fields, $form_id, $next = 0, $args = array() ) {
$base_name = 'frm_form_fields_' . $form_id . $args['inc_embed'] . $args['inc_repeat'];
$field_chunks = array_chunk( $fields, self::$transient_size );
foreach ( $field_chunks as $field ) {
$name = $next ? $base_name . $next : $base_name;
$set = set_transient( $name, $field, 60 * 60 * 6 );
if ( ! $set ) {
// the transient didn't save
if ( $name != $base_name ) {
// if the first saved an others fail, this will show an incmoplete form
self::delete_form_transient( $form_id );
}
return;
}
++$next;
}
}
/**
* @param string $type
* @return bool
*/
public static function is_no_save_field( $type ) {
return in_array( $type, self::no_save_fields(), true );
}
/**
* @return string[]
*/
public static function no_save_fields() {
return array( 'divider', 'end_divider', 'captcha', 'break', 'html', 'form', 'summary', FrmSubmitHelper::FIELD_TYPE );
}
/**
* Check if this field can hold an array of values
*
* @since 2.0.9
*
* @param array|object $field
*
* @return bool
*/
public static function is_field_with_multiple_values( $field ) {
if ( ! $field ) {
return false;
}
$field_type = self::get_original_field_type( $field );
$is_multi_value_field = (
self::is_checkbox( $field ) ||
$field_type === 'address' ||
self::is_multiple_select( $field )
);
return $is_multi_value_field;
}
/**
* @since 3.0
* @param array|object $field
* @return string
*/
public static function get_field_type( $field ) {
return is_array( $field ) ? $field['type'] : $field->type;
}
/**
* @since 3.0
* @return string
*/
public static function get_original_field_type( $field ) {
$field_type = self::get_field_type( $field );
$original_type = self::get_option( $field, 'original_type' );
if ( ! empty( $original_type ) && $original_type != $field_type ) {
// Check the original type for arrays.
$field_type = $original_type;
}
return $field_type;
}
/**
* Check if this is a multiselect dropdown field
*
* @since 2.0.9
* @return bool
*/
public static function is_multiple_select( $field ) {
$field_type = self::get_field_type( $field );
$is_multiple = self::is_option_true( $field, 'multiple' ) && self::is_field_type( $field, 'select' ) && $field_type !== 'hidden';
return apply_filters( 'frm_is_multiple_select', $is_multiple, $field );
}
/**
* Check if a field is read only. Read only can be set in the field options,
* but disabled with the shortcode options
*
* @since 2.0.9
*
* @param array|object $field
* @return bool
*/
public static function is_read_only( $field ) {
global $frm_vars;
return self::is_option_true( $field, 'read_only' ) && ( ! isset( $frm_vars['readonly'] ) || $frm_vars['readonly'] !== 'disabled' );
}
/**
* @since 2.0.9
*
* @param array $field
* @return bool
*/
public static function is_required( $field ) {
$required = $field['required'] != '0';
/**
* @param bool $required
* @param array $field
*/
$required = (bool) apply_filters( 'frm_is_field_required', $required, $field );
return $required;
}
/**
* @since 2.0.9
*
* @param array|object $field
* @param string $option
* @return bool
*/
public static function is_option_true( $field, $option ) {
if ( is_array( $field ) ) {
return self::is_option_true_in_array( $field, $option );
}
return self::is_option_true_in_object( $field, $option );
}
/**
* @since 2.0.9
*
* @param array|object $field
* @param string $option
* @return bool
*/
public static function is_option_empty( $field, $option ) {
if ( is_array( $field ) ) {
return self::is_option_empty_in_array( $field, $option );
}
return self::is_option_empty_in_object( $field, $option );
}
/**
* @param array $field
* @param string $option
* @return bool
*/
public static function is_option_true_in_array( $field, $option ) {
return ! empty( $field[ $option ] );
}
/**
* @param object $field
* @param string $option
* @return bool
*/
public static function is_option_true_in_object( $field, $option ) {
return isset( $field->field_options[ $option ] ) && $field->field_options[ $option ];
}
/**
* @param array $field
* @param string $option
* @return bool
*/
public static function is_option_empty_in_array( $field, $option ) {
return empty( $field[ $option ] );
}
/**
* @param object $field
* @param string $option
* @return bool
*/
public static function is_option_empty_in_object( $field, $option ) {
return empty( $field->field_options[ $option ] );
}
/**
* @param stdClass $field
* @param string $option
* @return bool
*/
public static function is_option_value_in_object( $field, $option ) {
return isset( $field->field_options[ $option ] ) && $field->field_options[ $option ] != '';
}
/**
* @since 2.0.18
*
* @param array|object $field
* @param string $option
* @return mixed
*/
public static function get_option( $field, $option ) {
if ( is_array( $field ) ) {
$option = self::get_option_in_array( $field, $option );
} else {
$option = self::get_option_in_object( $field, $option );
}
return $option;
}
/**
* @param array $field
* @param string $option
* @return mixed
*/
public static function get_option_in_array( $field, $option ) {
if ( isset( $field[ $option ] ) ) {
$this_option = $field[ $option ];
} elseif ( isset( $field['field_options'] ) && is_array( $field['field_options'] ) && isset( $field['field_options'][ $option ] ) ) {
$this_option = $field['field_options'][ $option ];
} else {
$this_option = '';
}
return $this_option;
}
/**
* @param object $field
* @param string $option
* @return mixed
*/
public static function get_option_in_object( $field, $option ) {
return isset( $field->field_options[ $option ] ) ? $field->field_options[ $option ] : '';
}
/**
* @since 2.0.09
*
* @param array|object $field
* @return bool
*/
public static function is_repeating_field( $field ) {
if ( is_array( $field ) ) {
$is_repeating_field = ( 'divider' === $field['type'] );
} else {
$is_repeating_field = ( 'divider' === $field->type );
}
return $is_repeating_field && self::is_option_true( $field, 'repeat' );
}
/**
* @param string $key
*
* @return int field id
*/
public static function get_id_by_key( $key ) {
$id = FrmDb::get_var( 'frm_fields', array( 'field_key' => sanitize_title( $key ) ) );
return (int) $id;
}
/**
* @param string $id
*
* @return string|null
*/
public static function get_key_by_id( $id ) {
return FrmDb::get_var( 'frm_fields', array( 'id' => $id ), 'field_key' );
}
public static function is_image( $field ) {
$type = self::get_field_type( $field );
return ( $type === 'url' && self::get_option( $field, 'show_image' ) );
}
/**
* Check if field is radio or Dynamic radio
*
* @since 3.0
*
* @param array|object $field
*
* @return bool true if field type is radio or Dynamic radio
*/
public static function is_radio( $field ) {
return self::is_field_type( $field, 'radio' );
}
/**
* Check if field is checkbox or Dynamic checkbox
*
* @since 3.0
*
* @param array|object $field
*
* @return bool true if field type is checkbox or Dynamic checkbox
*/
public static function is_checkbox( $field ) {
return self::is_field_type( $field, 'checkbox' );
}
/**
* Check if field is checkbox or radio
*
* @since 3.0
*
* @param array|object $field
* @param string $is_type Options include radio, checkbox, text.
*
* @return bool true if field type is checkbox or Dynamic checkbox
*/
public static function is_field_type( $field, $is_type ) {
$field_type = self::get_original_field_type( $field );
$data_type = self::get_option( $field, 'data_type' );
$is_field_type = (
$is_type === $field_type ||
( 'data' === $field_type && $is_type === $data_type ) ||
( 'lookup' === $field_type && $is_type === $data_type ) ||
( 'product' === $field_type && $is_type === $data_type )
);
/**
* When a field type is checked, allow individual fields
* to set the type.
*
* @since 4.04
*/
return apply_filters( 'frm_is_field_type', $is_field_type, compact( 'field', 'is_type' ) );
}
/**
* Checks if the given field array is a combo field.
*
* @since 4.10.02
*
* @param array $field Field array.
* @return bool
*/
public static function is_combo_field( $field ) {
$field_type_obj = FrmFieldFactory::get_field_factory( $field );
return ! empty( $field_type_obj->is_combo_field );
}
}