# formidable/trunk/classes/models/fields/FrmFieldAddress.php

Formidable Forms – WordPress Form Builder for Contact Forms, Calculators, Quizzes &amp; More, version trunk. 476 lines.

- Page: https://pluginprobe.com/plugins/formidable/trunk/code/classes/models/fields/FrmFieldAddress.php
- Raw: https://pluginprobe.com/plugins/formidable/trunk/raw/classes/models/fields/FrmFieldAddress.php
- Modified: 2026-09-09T16:18:04+00:00

Line numbers below start at 1. Link to a line or a range by appending a fragment to the
page URL, for example `https://pluginprobe.com/plugins/formidable/trunk/code/classes/models/fields/FrmFieldAddress.php#L10-L20`.

```php
<?php

if ( ! defined( 'ABSPATH' ) ) {
	die( 'You are not allowed to call this page directly.' );
}

/**
 * Address field type.
 *
 * @since 6.34
 */
class FrmFieldAddress extends FrmFieldCombo {

	/**
	 * @var string
	 *
	 * @since 6.34
	 */
	protected $type = 'address';

	/**
	 * This is used to check if field is combo field.
	 *
	 * @var bool
	 *
	 * @since 6.34
	 */
	public $is_combo_field = true;

	/**
	 * Constructor - register sub-fields.
	 *
	 * @since 6.34
	 *
	 * @param array|int|object $field
	 * @param string           $type
	 */
	public function __construct( $field = 0, $type = '' ) {
		parent::__construct( $field, $type );
		$this->register_sub_fields( self::get_part_labels() );
	}

	/**
	 * Gets the label for each part of an address, keyed by the value the part is
	 * shown with in a shortcode, like [25 show=city].
	 *
	 * @since 6.35
	 *
	 * @return array
	 */
	public static function get_part_labels() {
		return array(
			'line1'   => __( 'Line 1', 'formidable' ),
			'line2'   => __( 'Line 2', 'formidable' ),
			'city'    => __( 'City', 'formidable' ),
			'state'   => __( 'State/Province', 'formidable' ),
			'zip'     => __( 'Zip/Postal', 'formidable' ),
			'country' => __( 'Country', 'formidable' ),
		);
	}

	protected function field_settings_for_type() {
		return array(
			'clear_on_focus' => false,
			'description'    => false,
			'default'        => false,
		);
	}

	protected function extra_field_opts() {
		$options                 = parent::extra_field_opts();
		$options['address_type'] = 'international';

		// Default descriptions for sub-fields (line1 and line2 are empty).
		$options['line1_desc']   = '';
		$options['line2_desc']   = '';
		$options['city_desc']    = __( 'City', 'formidable' );
		$options['state_desc']   = __( 'State/Province', 'formidable' );
		$options['zip_desc']     = __( 'Zip/Postal', 'formidable' );
		$options['country_desc'] = __( 'Country', 'formidable' );

		return $options;
	}

	/**
	 * Get processed sub-fields based on address type.
	 *
	 * @since 6.34
	 *
	 * @return array
	 */
	protected function get_processed_sub_fields() {
		$address_type = FrmField::get_option( $this->field, 'address_type' );
		$sub_fields   = $this->sub_fields;

		// Set default classes and attributes
		$default_atts = array(
			'line1'   => array( 'autocomplete' => 'address-line1' ),
			'line2'   => array(
				'autocomplete' => 'address-line2',
				'optional'     => true,
			),
			'city'    => array(
				'autocomplete'    => 'address-level2',
				'wrapper_classes' => 'frm_third frm_first',
			),
			'state'   => array(
				'autocomplete'    => 'address-level1',
				'wrapper_classes' => 'frm_third',
			),
			'zip'     => array(
				'autocomplete'    => 'postal-code',
				'wrapper_classes' => 'frm_third',
			),
			'country' => array( 'autocomplete' => 'country-name' ),
		);

		foreach ( $sub_fields as $name => &$sub_field ) {
			if ( isset( $default_atts[ $name ] ) ) {
				$sub_field = array_merge( $sub_field, $default_atts[ $name ] );
			}

			// Ensure label is preserved from registered sub-fields.
			if ( ! isset( $sub_field['label'] ) ) {
				$sub_field['label'] = $this->sub_fields[ $name ]['label'] ?? '';
			}
		}

		// Handle Europe address type (no state field, zip shown before city)
		if ( 'europe' === $address_type ) {
			$city_field    = $sub_fields['city'];
			$country_field = $sub_fields['country'];
			unset( $sub_fields['state'], $sub_fields['city'], $sub_fields['country'] );
			$sub_fields['city']    = $city_field;
			$sub_fields['country'] = $country_field;

			$sub_fields['city']['wrapper_classes'] = 'frm_third';
			$sub_fields['zip']['wrapper_classes']  = 'frm_third frm_first';
		}

		// Handle US address type (state dropdown, no country field)
		if ( 'us' === $address_type ) {
			$sub_fields['state']['type']    = 'select';
			$sub_fields['state']['options'] = FrmFieldsHelper::get_us_states();
			unset( $sub_fields['country'] );
		}

		// International and Europe address types use a country dropdown
		if ( in_array( $address_type, array( 'international', 'europe' ), true ) ) {
			$sub_fields['country']['type']    = 'select';
			$sub_fields['country']['options'] = FrmFieldsHelper::get_countries();
		}

		// Generic type has no country dropdown
		if ( 'generic' === $address_type ) {
			unset( $sub_fields['country'] );
		}

		return $sub_fields;
	}

	/**
	 * @since 6.34
	 *
	 * @param array $args - Includes 'field', 'display', and 'values'.
	 */
	public function show_primary_options( $args ) {
		$field = $args['field'];

		include FrmAppHelper::plugin_path() . '/classes/views/combo-fields/addresses/back-end-field-opts.php';

		parent::show_primary_options( $args );
	}

	/**
	 * @param array $atts
	 *
	 * @return void
	 */
	protected function fill_default_atts( &$atts ) {
		parent::fill_default_atts( $atts );
		$defaults = array(
			'line_sep'    => ' <br/>',
			'force_array' => false,
		);
		$atts     = wp_parse_args( $atts, $defaults );
	}

	protected function prepare_display_value( $value, $atts ) {
		if ( ! is_array( $value ) ) {
			return $value;
		}

		$keys_to_check                = array( 'line1', 'city', 'state', 'zip', 'country' );
		$at_least_one_field_populated = false;

		foreach ( $keys_to_check as $key ) {
			if ( ! empty( $value[ $key ] ) ) {
				$at_least_one_field_populated = true;
				break;
			}
		}

		if ( ! $at_least_one_field_populated ) {
			return '';
		}

		if ( ! isset( $value['line1'] ) ) {
			$value['line1'] = '';
		}

		return $this->format_address_for_display( $value, $atts );
	}

	/**
	 * @since 6.34
	 *
	 * @param array $value
	 * @param array $atts
	 *
	 * @return array|string
	 */
	public function format_address_for_display( $value, $atts = array() ) {
		$defaults = $this->empty_value_array();
		$this->fill_values( $value, $defaults );

		$this->fill_default_atts( $atts );

		if ( $atts['force_array'] ) {
			return $value;
		}

		$format = $this->address_format_for_display( $atts, $value );

		foreach ( $defaults as $k => $part ) {
			$format = str_replace( '[' . $k . ']', $value[ $k ], $format );
		}

		return str_replace( $atts['line_sep'] . $atts['line_sep'], $atts['line_sep'], $format );
	}

	/**
	 * @since 6.34
	 *
	 * @param array $atts
	 * @param array $value
	 *
	 * @return string
	 */
	private function address_format_for_display( $atts, $value ) {
		if ( ! empty( $atts['show'] ) ) {
			return '[' . $atts['show'] . ']';
		}

		$line_sep       = $atts['line_sep'];
		$address_type   = FrmField::get_option( $this->field, 'address_type' );
		$address_format = '';

		if ( ! empty( $value['line1'] ) ) {
			$address_format = '[line1]' . $line_sep . '[line2]' . $line_sep;
		}

		if ( 'europe' === $address_type ) {
			$address_format .= '[zip] [city]';
		} else {
			$address_format .= '[city], [state] [zip]';
		}

		$address_format .= $line_sep . '[country]';

		/**
		 * Change the format of a displayed address
		 *
		 * @since 6.34
		 *
		 * @param string $address_format
		 * @param array  $args
		 */
		$result = apply_filters( 'frm_address_format', $address_format, array( 'field' => $this->field ) );

		return is_string( $result ) ? $result : $address_format;
	}

	/**
	 * @since 6.34
	 *
	 * @param mixed $value
	 *
	 * @return array
	 */
	public function address_string_to_array( $value ) {
		if ( is_array( $value ) ) {
			return $value;
		}

		if ( ! is_string( $value ) ) {
			$empty_array = array_keys( $this->empty_value_array() );
			return array_combine( $empty_array, array_fill( 0, count( $empty_array ), '' ) );
		}

		$value             = array_map( 'trim', explode( ',', $value ) );
		$empty_array       = array_keys( $this->empty_value_array() );
		$array_length_diff = count( $empty_array ) - count( $value );

		if ( $array_length_diff === 1 ) {
			$value[] = '';
		} elseif ( $array_length_diff > 1 ) {
			$filler_array = array_fill( 0, $array_length_diff, '' );
			$value        = array_merge( $value, $filler_array );
		} elseif ( $array_length_diff < 0 ) {
			$value = array_slice( $value, 0, count( $empty_array ) );
		}

		return array_combine( $empty_array, $value );
	}

	/**
	 * Get empty value array for address.
	 *
	 * @since 6.34
	 *
	 * @return array
	 */
	private function empty_value_array() {
		/**
		 * @since 6.34
		 *
		 * @param array $empty_value_array array of empty address data.
		 */
		/** @var array */
		$result = apply_filters(
			'frm_address_empty_value_array',
			array(
				'line1'   => '',
				'line2'   => '',
				'city'    => '',
				'state'   => '',
				'zip'     => '',
				'country' => '',
			)
		);

		return is_array( $result ) ? $result : array(
			'line1'   => '',
			'line2'   => '',
			'city'    => '',
			'state'   => '',
			'zip'     => '',
			'country' => '',
		);
	}

	/**
	 * Convert comma-separated address values to an associative array
	 *
	 * @since 6.34
	 *
	 * @param array|string $value
	 * @param array        $atts
	 *
	 * @return array
	 */
	protected function prepare_import_value( $value, $atts ) {
		if ( is_array( $value ) ) {
			return $value;
		}

		$sep   = apply_filters( 'frm_csv_sep', ', ' );
		$sep   = is_string( $sep ) && $sep !== '' ? $sep : ', ';
		$value = explode( $sep, $value );
		$count = count( $value );

		if ( $count < 4 || $count > 6 ) {
			return $value;
		}

		$new_value = array(
			'line1'   => '',
			'line2'   => '',
			'city'    => '',
			'state'   => '',
			'zip'     => '',
			'country' => '',
		);

		$new_value['line1'] = $value[0];
		$last_item          = end( $value );

		if ( $count === 6 || ( $count === 5 && is_numeric( $last_item ) ) ) {
			$new_value['line2'] = $value[1];
			$new_value['city']  = $value[2];
			$new_value['state'] = $value[3];
			$new_value['zip']   = $value[4];

			if ( $count === 6 ) {
				$new_value['country'] = $value[5];
			}
		} else {
			$new_value['city']  = $value[1];
			$new_value['state'] = $value[2];
			$new_value['zip']   = $value[3];

			if ( $count === 5 ) {
				$new_value['country'] = $value[4];
			}
		}

		return $new_value;
	}

	/**
	 * Validate field.
	 *
	 * @since 6.34
	 *
	 * @param array $args Arguments. Includes `errors`, `value`.
	 *
	 * @return array Errors array.
	 */
	public function validate( $args ) {
		$errors = parent::validate( $args );

		// Add zip validation for US addresses
		$values = $args['value'];

		if ( empty( $values['zip'] ) ) {
			return $errors;
		}

		$address_type = FrmField::get_option( $this->field, 'address_type' );
		$format       = '';

		if ( 'us' === $address_type ) {
			$format = '/^[0-9]{5}(?:-[0-9]{4})?$/';
		}

		$format = apply_filters( 'frm_zip_format', $format, array( 'field' => $this->field ) );

		if ( $format && ! preg_match( $format, $values['zip'] ) ) {
			$errors[ 'field' . $args['id'] . '-zip' ] = __( 'This value is invalid', 'formidable' );
		}

		return $errors;
	}

	/**
	 * @since 6.34
	 *
	 * @param mixed $value
	 */
	public function sanitize_value( &$value ) {
		FrmAppHelper::sanitize_value( 'sanitize_text_field', $value );
	}

	/**
	 * Get export headings for address field.
	 *
	 * @since 6.34
	 *
	 * @return array
	 */
	public function get_export_headings() {
		$field_id   = is_object( $this->field ) ? $this->field->id : $this->field['id'];
		$field_name = is_object( $this->field ) ? $this->field->name : $this->field['name'];

		// Include main field heading at the front.
		$headings = array(
			$field_id => $field_name,
		);

		// Include sub-field headings. Use the union operator instead of array_merge
		// so the numeric main field key (e.g. 10) is preserved and not re-indexed.
		return $headings + parent::get_export_headings();
	}
}

```
