: str_replace(): Passing null to parameter #2 ($replace) of type array|string is deprecated in
// phpcs:ignore WPForms.PHP.UseStatement.UnusedUseStatement
use \WPForms\Forms\Fields\Base\Frontend as FrontendBase;
use WPForms\Forms\Fields\Helpers\RequirementsAlerts;
use WPForms\Forms\IconChoices;
abstract class WPForms_Field {
* Full name of the field type, eg "Paragraph Text".
* Type of the field, eg "textarea".
* Font Awesome Icon used for the editor button, eg "fa-list".
* Field keywords for search, eg "checkbox, file, icon, upload".
* Priority order the field button should show inside the "Add Fields" tab.
* Field group the field belongs to.
public $group = 'standard';
* Placeholder to hold default value(s) for some field types.
* Current form ID in the admin builder.
* Instance of the Frontend class.
* Primary class constructor.
* @param bool $init Pass false to allow to shortcut the whole initialization, if needed.
public function __construct( $init = true ) {
// phpcs:disable WordPress.Security.NonceVerification
if ( isset( $_GET['form_id'] ) ) {
$this->form_id = absint( $_GET['form_id'] );
} elseif ( isset( $_POST['id'] ) ) {
$this->form_id = absint( $_POST['id'] );
// phpcs:enable WordPress.Security.NonceVerification
// Initialize field's Frontend class.
$this->frontend_obj = $this->get_object( 'Frontend' );
// Temporary solution to get an object of the field class.
"wpforms_fields_get_field_object_{$this->type}",
add_filter( 'wpforms_field_data', [ $this, 'field_data' ], 10, 2 );
add_filter( 'wpforms_builder_fields_buttons', [ $this, 'field_button' ], 15 );
// Add field keywords to the template fields.
add_filter( 'wpforms_setup_template_fields', [ $this, 'enhance_template_fields_with_keywords' ] );
add_action( "wpforms_builder_fields_options_{$this->type}", [ $this, 'field_options' ], 10 );
add_action( "wpforms_builder_fields_previews_{$this->type}", [ $this, 'field_preview' ], 10 );
add_action( "wp_ajax_wpforms_new_field_{$this->type}", [ $this, 'field_new' ] );
// Display field input elements on front-end.
add_action( "wpforms_display_field_{$this->type}", [ $this, 'field_display_proxy' ], 10, 3 );
// Display field on back-end.
add_filter( "wpforms_pro_admin_entries_edit_is_field_displayable_{$this->type}", '__return_true', 9 );
add_action( "wpforms_process_validate_{$this->type}", [ $this, 'validate' ], 10, 3 );
add_action( "wpforms_process_format_{$this->type}", [ $this, 'format' ], 10, 3 );
add_filter( 'wpforms_field_properties', [ $this, 'field_prefill_value_property' ], 10, 3 );
// Change the choice's value while saving entries.
add_filter( 'wpforms_process_before_form_data', [ $this, 'field_fill_empty_choices' ] );
// Change field name for ajax error.
add_filter( 'wpforms_process_ajax_error_field_name', [ $this, 'ajax_error_field_name' ], 10, 4 );
// Add HTML line breaks before all newlines in Entry Preview.
add_filter( "wpforms_pro_fields_entry_preview_get_field_value_{$this->type}_field_after", 'nl2br', 100 );
// Add allowed HTML tags for the field label.
add_filter( 'wpforms_builder_strings', [ $this, 'add_allowed_label_html_tags' ] );
// Exclude empty dynamic choices from Entry Preview.
add_filter( 'wpforms_pro_fields_entry_preview_print_entry_preview_exclude_field', [ $this, 'exclude_empty_dynamic_choices' ], 10, 3 );
// Add classes to the builder field preview.
add_filter( 'wpforms_field_preview_class', [ $this, 'preview_field_class' ], 10, 2 );
* All systems go. Used by subclasses. Required.
* @since 1.5.0 Converted to abstract method, as it's required for all fields.
abstract public function init();
* Prefill field value with either fallback or dynamic data.
* This needs to be public (although internal) to be used in WordPress hooks.
* @param array $properties Field properties.
* @param array $field Current field specific data.
* @param array $form_data Prepared form data/settings.
* @return array Modified field properties.
public function field_prefill_value_property( $properties, $field, $form_data ) {
// Process only for current field.
if ( $this->type !== $field['type'] ) {
// Set the form data, so we can reuse it later, even on front-end.
$this->form_data = $form_data;
if ( ! empty( $this->form_data['settings']['dynamic_population'] ) ) {
$properties = $this->field_prefill_value_property_dynamic( $properties, $field );
// Fallback data, rewrites dynamic because user-submitted data is more important.
$properties = $this->field_prefill_value_property_fallback( $properties, $field );
* As we are processing user submitted data - ignore all admin-defined defaults.
* Preprocess choices-related fields only.
* @param array $field Field data and settings.
* @param array $properties Properties we are modifying.
public function field_prefill_remove_choices_defaults( $field, &$properties ) {
// Skip this step on admin page.
if ( is_admin() && ! wpforms_is_admin_page( 'entries', 'edit' ) ) {
! empty( $field['dynamic_choices'] ) ||
! empty( $field['choices'] )
function ( &$value, $key ) {
if ( 'default' === $key ) {
if ( 'wpforms-selected' === $value ) {
* Whether current field can be populated dynamically.
* @param array $properties Field properties.
* @param array $field Current field specific data.
public function is_dynamic_population_allowed( $properties, $field ) {
// Allow population on front-end only.
// For dynamic population we require $_GET.
if ( empty( $_GET ) ) { // phpcs:ignore
return apply_filters( 'wpforms_field_is_dynamic_population_allowed', $allowed, $properties, $field );
* Prefill the field value with a dynamic value, that we get from $_GET.
* The pattern is: wpf4_12_primary, where:
* As 'primary' is our default input key, "wpf4_12_primary" and "wpf4_12" are the same.
* @param array $properties Field properties.
* @param array $field Current field specific data.
* @return array Modified field properties.
protected function field_prefill_value_property_dynamic( $properties, $field ) {
if ( ! $this->is_dynamic_population_allowed( $properties, $field ) ) {
// Iterate over each GET key, parse, and scrap data from there.
foreach ( $_GET as $key => $raw_value ) { // phpcs:ignore
preg_match( '/wpf(\d+)_(\d+)(.*)/i', $key, $matches );
if ( empty( $matches ) || ! is_array( $matches ) ) {
$form_id = absint( $matches[1] );
$field_id = absint( $matches[2] );
if ( ! empty( $matches[3] ) ) {
$input = sanitize_key( trim( $matches[3], '_' ) );
// Both form and field IDs should be the same as current form/field.
(int) $this->form_data['id'] !== $form_id ||
(int) $field['id'] !== $field_id
// Go to the next GET param.
if ( ! empty( $raw_value ) ) {
$this->field_prefill_remove_choices_defaults( $field, $properties );
* Some fields (like checkboxes) support multiple selection.
* We do not support nested values, so omit them.
* Example: ?wpf771_19_wpforms[fields][19][address1]=test
* $raw_value = [fields=>[]]
* $single_value = [19=>[]]
* There is no reliable way to clean those things out.
* So we will ignore the value altogether if it's an array.
* We support only single value numeric arrays, like these:
* ?wpf771_19[]=test1&wpf771_19[]=test2
* ?wpf771_19_value[]=test1&wpf771_19_value[]=test2
* ?wpf771_41_r3_c2[]=1&wpf771_41_r1_c4[]=1
if ( is_array( $raw_value ) ) {
foreach ( $raw_value as $single_value ) {
$properties = $this->get_field_populated_single_property_value( $single_value, $input, $properties, $field );
$properties = $this->get_field_populated_single_property_value( $raw_value, $input, $properties, $field );
* Public version of get_field_populated_single_property_value() to use by external classes.
* @param string $raw_value Value from a GET param, always a string.
* @param string $input Represent a subfield inside the field. May be empty.
* @param array $properties Field properties.
* @param array $field Current field specific data.
* @return array Modified field properties.
public function get_field_populated_single_property_value_public( $raw_value, $input, $properties, $field ) {
return $this->get_field_populated_single_property_value( $raw_value, $input, $properties, $field );
* Get the value, that is used to prefill via dynamic or fallback population.
* Based on field data and current properties.
* @param string $raw_value Value from a GET param, always a string.
* @param string $input Represent a subfield inside the field. May be empty.
* @param array $properties Field properties.
* @param array $field Current field specific data.
* @return array Modified field properties.
protected function get_field_populated_single_property_value( $raw_value, $input, $properties, $field ) {
if ( ! is_string( $raw_value ) ) {
$get_value = stripslashes( sanitize_text_field( $raw_value ) );
// For fields that have dynamic choices we need to add extra logic.
if ( ! empty( $field['dynamic_choices'] ) ) {
$properties = $this->get_field_populated_single_property_value_dynamic_choices( $get_value, $properties );
} elseif ( ! empty( $field['choices'] ) && is_array( $field['choices'] ) ) {
$properties = $this->get_field_populated_single_property_value_normal_choices( $get_value, $properties, $field );
* For other types of fields we need to check that
* the key is registered for the defined field in inputs array.
isset( $properties['inputs'][ $input ] )
$properties['inputs'][ $input ]['attr']['value'] = $get_value;
* Get the value, that is used to prefill via dynamic or fallback population.
* Based on field data and current properties.
* Dynamic choices section.
* @param string $get_value Value from a GET param, always a string, sanitized, stripped slashes.
* @param array $properties Field properties.
* @return array Modified field properties.
protected function get_field_populated_single_property_value_dynamic_choices( $get_value, $properties ) {
foreach ( $properties['inputs'] as $input_key => $input_arr ) {
// Dynamic choices support only integers in its values.
if ( absint( $get_value ) === $input_arr['attr']['value'] ) {
$default_key = $input_key;
// Stop iterating over choices.
// Redefine default choice only if dynamic value has changed anything.
if ( null !== $default_key ) {
foreach ( $properties['inputs'] as $input_key => $choice_arr ) {
if ( $input_key === $default_key ) {
$properties['inputs'][ $input_key ]['default'] = true;
$properties['inputs'][ $input_key ]['container']['class'][] = 'wpforms-selected';
// Stop iterating over choices.
* Fill choices without labels.