: str_replace(): Passing null to parameter #2 ($replace) of type array|string is deprecated in
* @param array $form_data Form data.
public function field_fill_empty_choices( $form_data ) {
if ( empty( $form_data['fields'] ) ) {
// Set value for choices with the image only. Conditional logic doesn't work without value.
foreach ( $form_data['fields'] as $field_key => $field ) {
// Payment fields have their labels set up upfront.
if ( empty( $field['choices'] ) || ! in_array( $field['type'], [ 'radio', 'checkbox' ], true ) ) {
foreach ( $field['choices'] as $choice_id => $choice ) {
if ( ( isset( $choice['value'] ) && '' !== trim( $choice['value'] ) ) || empty( $choice['image'] ) ) {
$form_data['fields'][ $field_key ]['choices'][ $choice_id ]['value'] = sprintf( /* translators: %d - choice number. */
esc_html__( 'Choice %d', 'wpforms-lite' ),
* Get the value, that is used to prefill via dynamic or fallback population.
* Based on field data and current properties.
* Normal choices section.
* @param string $get_value Value from a GET param, always a string, sanitized.
* @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_normal_choices( $get_value, $properties, $field ) {
// For fields that have normal choices we need to add extra logic.
foreach ( $field['choices'] as $choice_key => $choice_arr ) {
$choice_value_key = isset( $field['show_values'] ) ? 'value' : 'label';
isset( $choice_arr[ $choice_value_key ] ) &&
strtoupper( sanitize_text_field( $choice_arr[ $choice_value_key ] ) ) === strtoupper( $get_value )
empty( $choice_arr[ $choice_value_key ] ) &&
$get_value === sprintf( /* translators: %d - choice number. */
esc_html__( 'Choice %d', 'wpforms-lite' ),
$default_key = $choice_key;
// Stop iterating over choices.
// Redefine default choice only if population value has changed anything.
if ( null !== $default_key ) {
foreach ( $field['choices'] as $choice_key => $choice_arr ) {
if ( $choice_key === $default_key ) {
$properties['inputs'][ $choice_key ]['default'] = true;
$properties['inputs'][ $choice_key ]['container']['class'][] = 'wpforms-selected';
* Whether current field can be populated dynamically.
* @param array $properties Field properties.
* @param array $field Current field specific data.
public function is_fallback_population_allowed( $properties, $field ) {
// Allow population on front-end only.
* Commented out to allow partial fail for complex multi-inputs fields.
* Example: name field with first/last format and being required, filled out only first.
* On submit we will preserve those sub-inputs that are not empty and display an error for an empty.
// Do not populate if there are errors for that field.
$errors = wpforms()->get( 'process' )->errors;
if ( ! empty( $errors[ $this->form_data['id'] ][ $field['id'] ] ) ) {
// Require form id being the same for submitted and currently rendered form.
! empty( $_POST['wpforms']['id'] ) && // phpcs:ignore
(int) $_POST['wpforms']['id'] !== (int) $this->form_data['id'] // phpcs:ignore
// Require $_POST of submitted field.
if ( empty( $_POST['wpforms']['fields'] ) ) { // phpcs:ignore
// Require field (processed and rendered) being the same.
if ( ! isset( $_POST['wpforms']['fields'][ $field['id'] ] ) ) { // phpcs:ignore
return apply_filters( 'wpforms_field_is_fallback_population_allowed', $allowed, $properties, $field );
* Prefill the field value with a fallback value from form submission (in case of JS validation failed), that we get from $_POST.
* @param array $properties Field properties.
* @param array $field Current field specific data.
* @return array Modified field properties.
protected function field_prefill_value_property_fallback( $properties, $field ) {
if ( ! $this->is_fallback_population_allowed( $properties, $field ) ) {
if ( empty( $_POST['wpforms']['fields'] ) || ! is_array( $_POST['wpforms']['fields'] ) ) { // phpcs:ignore
// We got user submitted raw data (not processed, will be done later).
$raw_value = $_POST['wpforms']['fields'][ $field['id'] ]; // phpcs:ignore
if ( ! empty( $raw_value ) ) {
$this->field_prefill_remove_choices_defaults( $field, $properties );
* For this particular field this value may be either array or a string.
* In array - this is a complex field, like address.
* The key in array will be a sub-input (address1, state), and its appropriate value.
if ( is_array( $raw_value ) ) {
foreach ( $raw_value as $input => $single_value ) {
$properties = $this->get_field_populated_single_property_value( $single_value, sanitize_key( $input ), $properties, $field );
$properties = $this->get_field_populated_single_property_value( $raw_value, sanitize_key( $input ), $properties, $field );
* Get field data for the field.
* @param array $field Current field.
* @param array $form_data Form data and settings.
public function field_data( $field, $form_data ) {
// Remove field on frontend if it has no dynamic choices.
if ( $this->is_dynamic_choices_empty( $field, $form_data ) ) {
* Create the button for the 'Add Fields' tab, inside the form editor.
* @param array $fields List of form fields with their data.
public function field_button( $fields ) {
// Add field information to fields array.
$fields[ $this->group ]['fields'][] = [
'keywords' => $this->keywords,
* Enhances template fields by adding keywords.
* @param array $template_fields List of template fields.
public function enhance_template_fields_with_keywords( array $template_fields ): array {
foreach ( $template_fields as $key => $field ) {
if ( $field === $this->type ) {
$template_fields[ $key ] = $this->name;
$this->add_keywords( $template_fields );
return array_unique( $template_fields );
* Adds keywords to the provided fields.
* @param array $fields List of fields to which keywords will be added.
private function add_keywords( array &$fields ) {
$keywords_list = explode( ',', $this->keywords );
foreach ( $keywords_list as $keyword ) {
$fields[] = trim( $keyword );
* Create the field options panel. Used by subclasses.
* @since 1.5.0 Converted to abstract method, as it's required for all fields.
* @param array $field Field data and settings.
abstract public function field_options( $field );
* Create the field preview. Used by subclasses.
* @since 1.5.0 Converted to abstract method, as it's required for all fields.
* @param array $field Field data and settings.
abstract public function field_preview( $field );
* Helper function to create field option elements.
* Field option elements are pieces that help create a field option.
* They are used to quickly build field options.
* @param string $option Field option to render.
* @param array $field Field data and settings.
* @param array $args Field preview arguments.
* @param bool $echo Print or return the value. Print by default.
* @return mixed echo or return string
public function field_element( $option, $field, $args = [], $echo = true ) {
$id = (int) $field['id'];
$class = ! empty( $args['class'] ) ? wpforms_sanitize_classes( (array) $args['class'], true ) : '';
$slug = ! empty( $args['slug'] ) ? sanitize_title( $args['slug'] ) : '';
if ( ! empty( $args['data'] ) ) {
foreach ( $args['data'] as $arg_key => $val ) {
if ( is_array( $val ) ) {
$val = wp_json_encode( $val );
$attrs .= ' data-' . $arg_key . '=\'' . $val . '\'';
if ( ! empty( $args['attrs'] ) ) {
foreach ( $args['attrs'] as $arg_key => $val ) {
if ( is_array( $val ) ) {
$val = wp_json_encode( $val );
$attrs .= $arg_key . '=\'' . $val . '\'';
'<div class="wpforms-field-option-row wpforms-field-option-row-%s %s" id="wpforms-field-option-row-%d-%s" data-field-id="%s" %s>%s</div>',
$class = ! empty( $class ) ? ' class="' . $class . '"' : '';
$output = sprintf( '<label for="wpforms-field-option-%d-%s"%s>%s', $id, $slug, $class, esc_html( $args['value'] ) );
if ( isset( $args['tooltip'] ) && ! empty( $args['tooltip'] ) ) {
$output .= sprintf( '<i class="fa fa-question-circle-o wpforms-help-tooltip" title="%s"></i>', esc_attr( $args['tooltip'] ) );
if ( isset( $args['after_tooltip'] ) && ! empty( $args['after_tooltip'] ) ) {
$output .= $args['after_tooltip'];
$type = ! empty( $args['type'] ) ? esc_attr( $args['type'] ) : 'text';
$placeholder = ! empty( $args['placeholder'] ) ? esc_attr( $args['placeholder'] ) : '';
$before = ! empty( $args['before'] ) ? '<span class="before-input">' . esc_html( $args['before'] ) . '</span>' : '';
$after = ! empty( $args['after'] ) ? '<span class="after-input sub-label">' . esc_html( $args['after'] ) . '</span>' : '';
if ( ! empty( $before ) ) {
if ( ! empty( $after ) ) {
$output = sprintf( '%s<input type="%s" class="%s" id="wpforms-field-option-%d-%s" name="fields[%d][%s]" value="%s" placeholder="%s" %s>%s', $before, $type, $class, $id, $slug, $id, $slug, esc_attr( $args['value'] ), $placeholder, $attrs, $after );
$rows = ! empty( $args['rows'] ) ? (int) $args['rows'] : '3';
$output = sprintf( '<textarea class="%s" id="wpforms-field-option-%d-%s" name="fields[%d][%s]" rows="%d" %s>%s</textarea>', $class, $id, $slug, $id, $slug, $rows, $attrs, $args['value'] );
$checked = checked( '1', $args['value'], false );
$output = sprintf( '<input type="checkbox" class="%s" id="wpforms-field-option-%d-%s" name="fields[%d][%s]" value="1" %s %s>', $class, $id, $slug, $id, $slug, $checked, $attrs );
$output .= empty( $args['nodesc'] ) ? sprintf( '<label for="wpforms-field-option-%d-%s" class="inline">%s', $id, $slug, $args['desc'] ) : '';
if ( isset( $args['tooltip'] ) && ! empty( $args['tooltip'] ) ) {
$output .= sprintf( '<i class="fa fa-question-circle-o wpforms-help-tooltip" title="%s"></i>', esc_attr( $args['tooltip'] ) );
$output .= empty( $args['nodesc'] ) ? '</label>' : '';
$output = $this->field_element_toggle( $args, $id, $slug, $attrs, $class );
$options = $args['options'];
$value = isset( $args['value'] ) ? $args['value'] : '';
$output = sprintf( '<select class="%s" id="wpforms-field-option-%d-%s" name="fields[%d][%s]" %s>', $class, $id, $slug, $id, $slug, $attrs );
foreach ( $options as $arg_key => $arg_option ) {
$output .= sprintf( '<option value="%s" %s>%s</option>', esc_attr( $arg_key ), selected( $arg_key, $value, false ), $arg_option );
$args['class'][] = 'wpforms-color-picker';
$output = $this->field_element( 'text', $field, $args, $echo );
// @todo Ideally, we should late-escape here. All data above seems to be escaped or trusted, but we should consider refactoring this method.
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
* Create field option toggle element.
* @param array $args Arguments.
* @param integer $id Field ID.
* @param string $slug Field slug.
* @param string $attrs Attributes.
* @param string $class Class.
private function field_element_toggle( $args, $id, $slug, $attrs, $class ) {
'wpforms-field-option-%d-%s',
$label = ! empty( $args['desc'] ) ? $args['desc'] : '';
$value = ! empty( $args['value'] ) ? $args['value'] : '';
// Compatibility with the `checkbox` element.
$args['label-hide'] = ! empty( $args['nodesc'] ) ? $args['nodesc'] : false;
$args['input-class'] = $class;
return wpforms_panel_field_toggle_control( $args, $input_id, $field_name, $label, $value, $attrs );
* Helper function to create common field options that are used frequently.
* @param string $option Field option to render.
* @param array $field Field data and settings.
* @param array $args Field preview arguments.
* @param bool $echo Print or return the value. Print by default.
* @return mixed echo or return string
public function field_option( $option, $field, $args = [], $echo = true ) { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.MaxExceeded, Generic.Metrics.NestingLevel.MaxExceeded
$markup = ! empty( $args['markup'] ) ? $args['markup'] : 'open';
$class = ! empty( $args['class'] ) ? esc_html( $args['class'] ) : '';
if ( $markup === 'open' ) {
'<div class="wpforms-field-option-field-title">%3$s <span>(ID #%1$d)</span></div>
<div class="wpforms-field-option-group wpforms-field-option-group-basic active" id="wpforms-field-option-basic-%1$s">
<a href="#" class="wpforms-field-option-group-toggle">%2$s</a>
<div class="wpforms-field-option-group-inner %4$s">