PluginProbe
Formidable Forms – WordPress Form Builder for Contact Forms, Calculators, Quizzes & More / trunk
Formidable Forms – WordPress Form Builder for Contact Forms, Calculators, Quizzes & More vtrunk
6.35 6.34 6.33.1 6.33 6.32.1 6.32 6.31 6.25 6.25.1 6.26 6.26.1 6.27 6.28 6.29 6.3 6.3.1 6.3.2 6.30 6.4 6.4.1 6.4.2 6.5 6.5.1 6.5.2 6.5.3 All 141 releases
formidable / classes / models / fields / FrmFieldAddress.php

FrmFieldAddress.php in Formidable Forms – WordPress Form Builder for Contact Forms, Calculators, Quizzes & More trunk, at classes/models/fields/FrmFieldAddress.php

476 lines 11.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 if ( ! defined( 'ABSPATH' ) ) {
4 die( 'You are not allowed to call this page directly.' );
5 }
6
7 /**
8 * Address field type.
9 *
10 * @since 6.34
11 */
12 class FrmFieldAddress extends FrmFieldCombo {
13
14 /**
15 * @var string
16 *
17 * @since 6.34
18 */
19 protected $type = 'address';
20
21 /**
22 * This is used to check if field is combo field.
23 *
24 * @var bool
25 *
26 * @since 6.34
27 */
28 public $is_combo_field = true;
29
30 /**
31 * Constructor - register sub-fields.
32 *
33 * @since 6.34
34 *
35 * @param array|int|object $field
36 * @param string $type
37 */
38 public function __construct( $field = 0, $type = '' ) {
39 parent::__construct( $field, $type );
40 $this->register_sub_fields( self::get_part_labels() );
41 }
42
43 /**
44 * Gets the label for each part of an address, keyed by the value the part is
45 * shown with in a shortcode, like [25 show=city].
46 *
47 * @since 6.35
48 *
49 * @return array
50 */
51 public static function get_part_labels() {
52 return array(
53 'line1' => __( 'Line 1', 'formidable' ),
54 'line2' => __( 'Line 2', 'formidable' ),
55 'city' => __( 'City', 'formidable' ),
56 'state' => __( 'State/Province', 'formidable' ),
57 'zip' => __( 'Zip/Postal', 'formidable' ),
58 'country' => __( 'Country', 'formidable' ),
59 );
60 }
61
62 protected function field_settings_for_type() {
63 return array(
64 'clear_on_focus' => false,
65 'description' => false,
66 'default' => false,
67 );
68 }
69
70 protected function extra_field_opts() {
71 $options = parent::extra_field_opts();
72 $options['address_type'] = 'international';
73
74 // Default descriptions for sub-fields (line1 and line2 are empty).
75 $options['line1_desc'] = '';
76 $options['line2_desc'] = '';
77 $options['city_desc'] = __( 'City', 'formidable' );
78 $options['state_desc'] = __( 'State/Province', 'formidable' );
79 $options['zip_desc'] = __( 'Zip/Postal', 'formidable' );
80 $options['country_desc'] = __( 'Country', 'formidable' );
81
82 return $options;
83 }
84
85 /**
86 * Get processed sub-fields based on address type.
87 *
88 * @since 6.34
89 *
90 * @return array
91 */
92 protected function get_processed_sub_fields() {
93 $address_type = FrmField::get_option( $this->field, 'address_type' );
94 $sub_fields = $this->sub_fields;
95
96 // Set default classes and attributes
97 $default_atts = array(
98 'line1' => array( 'autocomplete' => 'address-line1' ),
99 'line2' => array(
100 'autocomplete' => 'address-line2',
101 'optional' => true,
102 ),
103 'city' => array(
104 'autocomplete' => 'address-level2',
105 'wrapper_classes' => 'frm_third frm_first',
106 ),
107 'state' => array(
108 'autocomplete' => 'address-level1',
109 'wrapper_classes' => 'frm_third',
110 ),
111 'zip' => array(
112 'autocomplete' => 'postal-code',
113 'wrapper_classes' => 'frm_third',
114 ),
115 'country' => array( 'autocomplete' => 'country-name' ),
116 );
117
118 foreach ( $sub_fields as $name => &$sub_field ) {
119 if ( isset( $default_atts[ $name ] ) ) {
120 $sub_field = array_merge( $sub_field, $default_atts[ $name ] );
121 }
122
123 // Ensure label is preserved from registered sub-fields.
124 if ( ! isset( $sub_field['label'] ) ) {
125 $sub_field['label'] = $this->sub_fields[ $name ]['label'] ?? '';
126 }
127 }
128
129 // Handle Europe address type (no state field, zip shown before city)
130 if ( 'europe' === $address_type ) {
131 $city_field = $sub_fields['city'];
132 $country_field = $sub_fields['country'];
133 unset( $sub_fields['state'], $sub_fields['city'], $sub_fields['country'] );
134 $sub_fields['city'] = $city_field;
135 $sub_fields['country'] = $country_field;
136
137 $sub_fields['city']['wrapper_classes'] = 'frm_third';
138 $sub_fields['zip']['wrapper_classes'] = 'frm_third frm_first';
139 }
140
141 // Handle US address type (state dropdown, no country field)
142 if ( 'us' === $address_type ) {
143 $sub_fields['state']['type'] = 'select';
144 $sub_fields['state']['options'] = FrmFieldsHelper::get_us_states();
145 unset( $sub_fields['country'] );
146 }
147
148 // International and Europe address types use a country dropdown
149 if ( in_array( $address_type, array( 'international', 'europe' ), true ) ) {
150 $sub_fields['country']['type'] = 'select';
151 $sub_fields['country']['options'] = FrmFieldsHelper::get_countries();
152 }
153
154 // Generic type has no country dropdown
155 if ( 'generic' === $address_type ) {
156 unset( $sub_fields['country'] );
157 }
158
159 return $sub_fields;
160 }
161
162 /**
163 * @since 6.34
164 *
165 * @param array $args - Includes 'field', 'display', and 'values'.
166 */
167 public function show_primary_options( $args ) {
168 $field = $args['field'];
169
170 include FrmAppHelper::plugin_path() . '/classes/views/combo-fields/addresses/back-end-field-opts.php';
171
172 parent::show_primary_options( $args );
173 }
174
175 /**
176 * @param array $atts
177 *
178 * @return void
179 */
180 protected function fill_default_atts( &$atts ) {
181 parent::fill_default_atts( $atts );
182 $defaults = array(
183 'line_sep' => ' <br/>',
184 'force_array' => false,
185 );
186 $atts = wp_parse_args( $atts, $defaults );
187 }
188
189 protected function prepare_display_value( $value, $atts ) {
190 if ( ! is_array( $value ) ) {
191 return $value;
192 }
193
194 $keys_to_check = array( 'line1', 'city', 'state', 'zip', 'country' );
195 $at_least_one_field_populated = false;
196
197 foreach ( $keys_to_check as $key ) {
198 if ( ! empty( $value[ $key ] ) ) {
199 $at_least_one_field_populated = true;
200 break;
201 }
202 }
203
204 if ( ! $at_least_one_field_populated ) {
205 return '';
206 }
207
208 if ( ! isset( $value['line1'] ) ) {
209 $value['line1'] = '';
210 }
211
212 return $this->format_address_for_display( $value, $atts );
213 }
214
215 /**
216 * @since 6.34
217 *
218 * @param array $value
219 * @param array $atts
220 *
221 * @return array|string
222 */
223 public function format_address_for_display( $value, $atts = array() ) {
224 $defaults = $this->empty_value_array();
225 $this->fill_values( $value, $defaults );
226
227 $this->fill_default_atts( $atts );
228
229 if ( $atts['force_array'] ) {
230 return $value;
231 }
232
233 $format = $this->address_format_for_display( $atts, $value );
234
235 foreach ( $defaults as $k => $part ) {
236 $format = str_replace( '[' . $k . ']', $value[ $k ], $format );
237 }
238
239 return str_replace( $atts['line_sep'] . $atts['line_sep'], $atts['line_sep'], $format );
240 }
241
242 /**
243 * @since 6.34
244 *
245 * @param array $atts
246 * @param array $value
247 *
248 * @return string
249 */
250 private function address_format_for_display( $atts, $value ) {
251 if ( ! empty( $atts['show'] ) ) {
252 return '[' . $atts['show'] . ']';
253 }
254
255 $line_sep = $atts['line_sep'];
256 $address_type = FrmField::get_option( $this->field, 'address_type' );
257 $address_format = '';
258
259 if ( ! empty( $value['line1'] ) ) {
260 $address_format = '[line1]' . $line_sep . '[line2]' . $line_sep;
261 }
262
263 if ( 'europe' === $address_type ) {
264 $address_format .= '[zip] [city]';
265 } else {
266 $address_format .= '[city], [state] [zip]';
267 }
268
269 $address_format .= $line_sep . '[country]';
270
271 /**
272 * Change the format of a displayed address
273 *
274 * @since 6.34
275 *
276 * @param string $address_format
277 * @param array $args
278 */
279 $result = apply_filters( 'frm_address_format', $address_format, array( 'field' => $this->field ) );
280
281 return is_string( $result ) ? $result : $address_format;
282 }
283
284 /**
285 * @since 6.34
286 *
287 * @param mixed $value
288 *
289 * @return array
290 */
291 public function address_string_to_array( $value ) {
292 if ( is_array( $value ) ) {
293 return $value;
294 }
295
296 if ( ! is_string( $value ) ) {
297 $empty_array = array_keys( $this->empty_value_array() );
298 return array_combine( $empty_array, array_fill( 0, count( $empty_array ), '' ) );
299 }
300
301 $value = array_map( 'trim', explode( ',', $value ) );
302 $empty_array = array_keys( $this->empty_value_array() );
303 $array_length_diff = count( $empty_array ) - count( $value );
304
305 if ( $array_length_diff === 1 ) {
306 $value[] = '';
307 } elseif ( $array_length_diff > 1 ) {
308 $filler_array = array_fill( 0, $array_length_diff, '' );
309 $value = array_merge( $value, $filler_array );
310 } elseif ( $array_length_diff < 0 ) {
311 $value = array_slice( $value, 0, count( $empty_array ) );
312 }
313
314 return array_combine( $empty_array, $value );
315 }
316
317 /**
318 * Get empty value array for address.
319 *
320 * @since 6.34
321 *
322 * @return array
323 */
324 private function empty_value_array() {
325 /**
326 * @since 6.34
327 *
328 * @param array $empty_value_array array of empty address data.
329 */
330 /** @var array */
331 $result = apply_filters(
332 'frm_address_empty_value_array',
333 array(
334 'line1' => '',
335 'line2' => '',
336 'city' => '',
337 'state' => '',
338 'zip' => '',
339 'country' => '',
340 )
341 );
342
343 return is_array( $result ) ? $result : array(
344 'line1' => '',
345 'line2' => '',
346 'city' => '',
347 'state' => '',
348 'zip' => '',
349 'country' => '',
350 );
351 }
352
353 /**
354 * Convert comma-separated address values to an associative array
355 *
356 * @since 6.34
357 *
358 * @param array|string $value
359 * @param array $atts
360 *
361 * @return array
362 */
363 protected function prepare_import_value( $value, $atts ) {
364 if ( is_array( $value ) ) {
365 return $value;
366 }
367
368 $sep = apply_filters( 'frm_csv_sep', ', ' );
369 $sep = is_string( $sep ) && $sep !== '' ? $sep : ', ';
370 $value = explode( $sep, $value );
371 $count = count( $value );
372
373 if ( $count < 4 || $count > 6 ) {
374 return $value;
375 }
376
377 $new_value = array(
378 'line1' => '',
379 'line2' => '',
380 'city' => '',
381 'state' => '',
382 'zip' => '',
383 'country' => '',
384 );
385
386 $new_value['line1'] = $value[0];
387 $last_item = end( $value );
388
389 if ( $count === 6 || ( $count === 5 && is_numeric( $last_item ) ) ) {
390 $new_value['line2'] = $value[1];
391 $new_value['city'] = $value[2];
392 $new_value['state'] = $value[3];
393 $new_value['zip'] = $value[4];
394
395 if ( $count === 6 ) {
396 $new_value['country'] = $value[5];
397 }
398 } else {
399 $new_value['city'] = $value[1];
400 $new_value['state'] = $value[2];
401 $new_value['zip'] = $value[3];
402
403 if ( $count === 5 ) {
404 $new_value['country'] = $value[4];
405 }
406 }
407
408 return $new_value;
409 }
410
411 /**
412 * Validate field.
413 *
414 * @since 6.34
415 *
416 * @param array $args Arguments. Includes `errors`, `value`.
417 *
418 * @return array Errors array.
419 */
420 public function validate( $args ) {
421 $errors = parent::validate( $args );
422
423 // Add zip validation for US addresses
424 $values = $args['value'];
425
426 if ( empty( $values['zip'] ) ) {
427 return $errors;
428 }
429
430 $address_type = FrmField::get_option( $this->field, 'address_type' );
431 $format = '';
432
433 if ( 'us' === $address_type ) {
434 $format = '/^[0-9]{5}(?:-[0-9]{4})?$/';
435 }
436
437 $format = apply_filters( 'frm_zip_format', $format, array( 'field' => $this->field ) );
438
439 if ( $format && ! preg_match( $format, $values['zip'] ) ) {
440 $errors[ 'field' . $args['id'] . '-zip' ] = __( 'This value is invalid', 'formidable' );
441 }
442
443 return $errors;
444 }
445
446 /**
447 * @since 6.34
448 *
449 * @param mixed $value
450 */
451 public function sanitize_value( &$value ) {
452 FrmAppHelper::sanitize_value( 'sanitize_text_field', $value );
453 }
454
455 /**
456 * Get export headings for address field.
457 *
458 * @since 6.34
459 *
460 * @return array
461 */
462 public function get_export_headings() {
463 $field_id = is_object( $this->field ) ? $this->field->id : $this->field['id'];
464 $field_name = is_object( $this->field ) ? $this->field->name : $this->field['name'];
465
466 // Include main field heading at the front.
467 $headings = array(
468 $field_id => $field_name,
469 );
470
471 // Include sub-field headings. Use the union operator instead of array_merge
472 // so the numeric main field key (e.g. 10) is preserved and not re-indexed.
473 return $headings + parent::get_export_headings();
474 }
475 }
476