PluginProbe
Formidable Forms – WordPress Form Builder for Contact Forms, Calculators, Quizzes & More / 6.26.1
Formidable Forms – WordPress Form Builder for Contact Forms, Calculators, Quizzes & More v6.26.1
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 / helpers / FrmXMLHelper.php

FrmXMLHelper.php in Formidable Forms – WordPress Form Builder for Contact Forms, Calculators, Quizzes & More 6.26.1, at classes/helpers/FrmXMLHelper.php

2,455 lines 68.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 if ( ! defined( 'ABSPATH' ) ) {
3 die( 'You are not allowed to call this page directly.' );
4 }
5
6 class FrmXMLHelper {
7
8 /**
9 * @var bool true if importing an XML from API, false if importing an XML file manually.
10 */
11 private static $installing_template = false;
12
13 /**
14 * @param array|string $opt
15 * @param string $padding
16 *
17 * @return void
18 */
19 public static function get_xml_values( $opt, $padding ) {
20 if ( is_array( $opt ) ) {
21 foreach ( $opt as $ok => $ov ) {
22 echo "\n" . esc_html( $padding );
23 $tag = ( is_numeric( $ok ) ? 'key:' : '' ) . $ok;
24 echo '<' . esc_html( $tag ) . '>';
25 self::get_xml_values( $ov, $padding . ' ' );
26
27 if ( is_array( $ov ) ) {
28 echo "\n" . esc_html( $padding );
29 }
30 echo '</' . esc_html( $tag ) . '>';
31 }
32 } else {
33 echo self::cdata( $opt ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
34 }
35 }
36
37 /**
38 * @param string $file
39 *
40 * @return array|WP_Error The items imported
41 */
42 public static function import_xml( $file ) {
43 if ( ! defined( 'WP_IMPORTING' ) ) {
44 define( 'WP_IMPORTING', true );
45 }
46
47 if ( ! class_exists( 'DOMDocument' ) ) {
48 $error_message = sprintf(
49 /* translators: 1: Documentation link */
50 __( 'In order to install XML, your server must have DOMDocument installed. Follow our documentation on %1$s to ensure DOMDocument is properly set up and XML support is enabled.', 'formidable' ),
51 '<a href="https://formidableforms.com/knowledgebase/import-forms-entries-and-views/#kb-your-server-does-not-have-xml-enabled" target="_blank">Importing Forms, Entries, and Views</a>'
52 );
53
54 return new WP_Error( 'SimpleXML_parse_error', $error_message, libxml_get_errors() );
55 }
56
57 $xml_string = file_get_contents( $file );
58 self::maybe_fix_xml( $xml_string );
59
60 $dom = new DOMDocument();
61
62 // LIBXML_COMPACT activates small nodes allocation optimization.
63 // Use LIBXML_PARSEHUGE to avoid "parser error : internal error: Huge input lookup" for large (300MB) files.
64 $success = $dom->loadXML( $xml_string, LIBXML_COMPACT | LIBXML_PARSEHUGE );
65
66 if ( ! $success ) {
67 return new WP_Error( 'SimpleXML_parse_error', __( 'There was an error when reading this XML file', 'formidable' ), libxml_get_errors() );
68 }
69
70 if ( ! function_exists( 'simplexml_import_dom' ) ) {
71 return new WP_Error( 'SimpleXML_parse_error', __( 'Your server is missing the simplexml_import_dom function', 'formidable' ), libxml_get_errors() );
72 }
73
74 $xml = simplexml_import_dom( $dom );
75 unset( $dom );
76
77 // halt if loading produces an error
78 if ( ! $xml ) {
79 return new WP_Error( 'SimpleXML_parse_error', __( 'There was an error when reading this XML file', 'formidable' ), libxml_get_errors() );
80 }
81
82 return self::import_xml_now( $xml );
83 }
84
85 /**
86 * @since 6.2.3
87 *
88 * @param string $xml_string
89 *
90 * @return void
91 */
92 private static function maybe_fix_xml( &$xml_string ) {
93 if ( '<?xml' !== substr( $xml_string, 0, 5 ) ) {
94 // Some XML files have may have unexpected characters at the start.
95 $xml_string = substr( $xml_string, strpos( $xml_string, '<?xml' ) );
96 }
97
98 // The Equity theme adds a <meta name="generator" content="Equity 1.7.13" /> tag using the "the_generator" filter.
99 // Strip that out as it breaks the XML import.
100 $channel_start_position = strpos( $xml_string, '<channel>' );
101 $content_before_channel_tag = substr( $xml_string, 0, $channel_start_position );
102
103 if ( 0 !== strpos( $content_before_channel_tag, '<meta name="generator" ' ) ) {
104 $content_before_channel_tag = preg_replace(
105 '/<meta\s+[^>]*name="generator"[^>]*\/>/i',
106 '',
107 $content_before_channel_tag,
108 1
109 );
110 $xml_string = $content_before_channel_tag . substr( $xml_string, $channel_start_position );
111 }
112 }
113
114 /**
115 * Add terms, forms (form and field ids), posts (post ids), and entries to db, in that order
116 *
117 * @since 3.06
118 *
119 * @param object $xml
120 * @param bool $installing_template
121 *
122 * @return array The number of items imported
123 */
124 public static function import_xml_now( $xml, $installing_template = false ) {
125 if ( ! defined( 'WP_IMPORTING' ) ) {
126 define( 'WP_IMPORTING', true );
127 }
128
129 self::$installing_template = $installing_template;
130 $imported = self::pre_import_data();
131
132 foreach ( array( 'term', 'form', 'view' ) as $item_type ) {
133 // Grab cats, tags, and terms, or forms or posts.
134 if ( isset( $xml->{$item_type} ) ) {
135 $function_name = 'import_xml_' . $item_type . 's';
136 $imported = self::$function_name( $xml->{$item_type}, $imported );
137 unset( $function_name, $xml->{$item_type} );
138 }
139 }
140
141 $imported = apply_filters( 'frm_importing_xml', $imported, $xml );
142
143 if ( empty( $imported['form_status'] ) ) {
144 // Check for an error message in the XML.
145 if ( isset( $xml->Code ) && isset( $xml->Message ) ) { // phpcs:ignore WordPress.NamingConventions
146 $imported['error'] = (string) $xml->Message; // phpcs:ignore WordPress.NamingConventions
147 }
148 }
149
150 return $imported;
151 }
152
153 /**
154 * @since 3.06
155 *
156 * @return array
157 */
158 private static function pre_import_data() {
159 $defaults = array(
160 'forms' => 0,
161 'fields' => 0,
162 'terms' => 0,
163 'posts' => 0,
164 'views' => 0,
165 'actions' => 0,
166 'styles' => 0,
167 );
168
169 return array(
170 'imported' => $defaults,
171 'updated' => $defaults,
172 'forms' => array(),
173 'terms' => array(),
174 );
175 }
176
177 /**
178 * @param SimpleXMLElement $terms
179 * @param array $imported
180 *
181 * @return array
182 */
183 public static function import_xml_terms( $terms, $imported ) {
184 foreach ( $terms as $t ) {
185 if ( term_exists( (string) $t->term_slug, (string) $t->term_taxonomy ) ) {
186 continue;
187 }
188
189 $parent = self::get_term_parent_id( $t );
190
191 $term = wp_insert_term(
192 (string) $t->term_name,
193 (string) $t->term_taxonomy,
194 array(
195 'slug' => (string) $t->term_slug,
196 'description' => (string) $t->term_description,
197 'parent' => empty( $parent ) ? 0 : $parent,
198 )
199 );
200
201 if ( $term && is_array( $term ) ) {
202 ++$imported['imported']['terms'];
203 $imported['terms'][ (int) $t->term_id ] = $term['term_id'];
204 }
205
206 unset( $term, $t );
207 }//end foreach
208
209 return $imported;
210 }
211
212 /**
213 * @since 2.0.8
214 *
215 * @param object $t
216 *
217 * @return int|string
218 */
219 private static function get_term_parent_id( $t ) {
220 $parent = (string) $t->term_parent;
221
222 if ( ! empty( $parent ) ) {
223 $parent = term_exists( (string) $t->term_parent, (string) $t->term_taxonomy );
224
225 if ( $parent ) {
226 $parent = $parent['term_id'];
227 } else {
228 $parent = 0;
229 }
230 }
231
232 return $parent;
233 }
234
235 /**
236 * @param SimpleXMLElement $forms
237 * @param array $imported
238 *
239 * @return array
240 */
241 public static function import_xml_forms( $forms, $imported ) {
242 $child_forms = array();
243
244 // Import child forms first
245 self::put_child_forms_first( $forms );
246
247 foreach ( $forms as $item ) {
248 $form = self::fill_form( $item );
249
250 self::update_custom_style_setting_on_import( $form );
251
252 $this_form = self::maybe_get_form( $form );
253
254 $old_id = false;
255 $form_fields = false;
256
257 if ( ! empty( $this_form ) ) {
258 $form_id = $this_form->id;
259 $old_id = $this_form->id;
260 self::update_form( $this_form, $form, $imported );
261
262 $form_fields = self::get_form_fields( $form_id );
263 } else {
264 $form_id = FrmForm::create( $form );
265
266 if ( $form_id ) {
267 if ( empty( $form['parent_form_id'] ) ) {
268 // Don't include the repeater form in the imported count.
269 ++$imported['imported']['forms'];
270 }
271
272 // Keep track of whether this specific form was updated or not.
273 $imported['form_status'][ $form_id ] = 'imported';
274 }
275 }
276
277 if ( $form_id ) {
278 self::track_imported_child_forms( (int) $form_id, $form['parent_form_id'], $child_forms );
279 }
280
281 self::import_xml_fields( $item->field, $form_id, $this_form, $form_fields, $imported );
282
283 self::delete_removed_fields( $form_fields );
284
285 // Update field ids/keys to new ones.
286 do_action( 'frm_after_duplicate_form', $form_id, $form, array( 'old_id' => $old_id ) );
287
288 $imported['forms'][ (int) $item->id ] = $form_id;
289
290 // Send pre 2.0 form options through function that creates actions.
291 self::migrate_form_settings_to_actions( $form['options'], $form_id, $imported, true );
292
293 do_action( 'frm_after_import_form', $form_id, $form );
294
295 unset( $form, $item );
296 }//end foreach
297
298 self::maybe_update_child_form_parent_id( $imported['forms'], $child_forms );
299
300 return $imported;
301 }
302
303 /**
304 * @param object $item
305 *
306 * @return array
307 */
308 private static function fill_form( $item ) {
309 $form = array(
310 'id' => (int) $item->id,
311 'form_key' => (string) $item->form_key,
312 'name' => (string) $item->name,
313 'description' => (string) $item->description,
314 'options' => (string) $item->options,
315 'logged_in' => (int) $item->logged_in,
316 'is_template' => (int) $item->is_template,
317 'editable' => (int) $item->editable,
318 'status' => (string) $item->status,
319 'parent_form_id' => isset( $item->parent_form_id ) ? (int) $item->parent_form_id : 0,
320 'created_at' => gmdate( 'Y-m-d H:i:s', strtotime( (string) $item->created_at ) ),
321 );
322
323 if ( empty( $item->created_at ) ) {
324 $form['created_at'] = current_time( 'mysql', 1 );
325 }
326
327 $form['options'] = FrmAppHelper::maybe_json_decode( $form['options'] );
328
329 if ( self::$installing_template ) {
330 // Templates don't necessarily have antispam on, but we want our templates to all have antispam on by default.
331 $form['options']['antispam'] = 1;
332 }
333
334 return $form;
335 }
336
337 /**
338 * @param array $form
339 *
340 * @return false|object
341 */
342 private static function maybe_get_form( $form ) {
343 // if template, allow to edit if form keys match, otherwise, creation date must also match
344 $edit_query = array(
345 'form_key' => $form['form_key'],
346 'is_template' => $form['is_template'],
347 );
348
349 if ( ! $form['is_template'] ) {
350 $edit_query['created_at'] = $form['created_at'];
351 }
352
353 $edit_query = apply_filters( 'frm_match_xml_form', $edit_query, $form );
354
355 $form = FrmForm::getAll( $edit_query, '', 1 );
356
357 if ( is_object( $form ) && $form->status === 'trash' ) {
358 FrmForm::destroy( $form->id );
359 return false;
360 }
361
362 return $form;
363 }
364
365 /**
366 * @param object $this_form
367 * @param array $form
368 * @param array $imported
369 *
370 * @return void
371 */
372 private static function update_form( $this_form, $form, &$imported ) {
373 $form_id = $this_form->id;
374 FrmForm::update( $form_id, $form );
375
376 if ( empty( $form['parent_form_id'] ) ) {
377 // Don't include the repeater form in the updated count.
378 ++$imported['updated']['forms'];
379 }
380
381 // Keep track of whether this specific form was updated or not
382 $imported['form_status'][ $form_id ] = 'updated';
383 }
384
385 /**
386 * @param int|string $form_id
387 *
388 * @return array
389 */
390 private static function get_form_fields( $form_id ) {
391 $form_fields = FrmField::get_all_for_form( $form_id, '', 'exclude', 'exclude' );
392 $old_fields = array();
393
394 foreach ( $form_fields as $f ) {
395 $old_fields[ $f->id ] = $f;
396 $old_fields[ $f->field_key ] = $f->id;
397 unset( $f );
398 }
399
400 $form_fields = $old_fields;
401
402 return $form_fields;
403 }
404
405 /**
406 * Delete any fields attached to this form that were not included in the template
407 *
408 * @param array $form_fields
409 *
410 * @return void
411 */
412 private static function delete_removed_fields( $form_fields ) {
413 if ( ! empty( $form_fields ) ) {
414 foreach ( $form_fields as $field ) {
415 if ( is_object( $field ) ) {
416 FrmField::destroy( $field->id );
417 }
418 unset( $field );
419 }
420 }
421 }
422
423 /**
424 * Put child forms first so they will be imported before parents
425 *
426 * @since 2.0.16
427 *
428 * @param array $forms
429 */
430 private static function put_child_forms_first( &$forms ) {
431 $child_forms = array();
432 $regular_forms = array();
433
434 foreach ( $forms as $form ) {
435 $parent_form_id = isset( $form->parent_form_id ) ? (int) $form->parent_form_id : 0;
436
437 if ( $parent_form_id ) {
438 $child_forms[] = $form;
439 } else {
440 $regular_forms[] = $form;
441 }
442 }
443
444 $forms = array_merge( $child_forms, $regular_forms );
445 }
446
447 /**
448 * Keep track of all imported child forms
449 *
450 * @since 2.0.16
451 *
452 * @param int $form_id
453 * @param int $parent_form_id
454 * @param array $child_forms
455 */
456 private static function track_imported_child_forms( $form_id, $parent_form_id, &$child_forms ) {
457 if ( $parent_form_id ) {
458 $child_forms[ $form_id ] = $parent_form_id;
459 }
460 }
461
462 /**
463 * Update the parent_form_id on imported child forms
464 * Child forms are imported first so their parent_form_id will need to be updated after the parent is imported
465 *
466 * @since 2.0.6
467 *
468 * @param array $imported_forms
469 * @param array $child_forms
470 */
471 private static function maybe_update_child_form_parent_id( $imported_forms, $child_forms ) {
472 foreach ( $child_forms as $child_form_id => $old_parent_form_id ) {
473 if ( isset( $imported_forms[ $old_parent_form_id ] ) && (int) $imported_forms[ $old_parent_form_id ] !== (int) $old_parent_form_id ) {
474 // Update all children with this old parent_form_id
475 $new_parent_form_id = (int) $imported_forms[ $old_parent_form_id ];
476 FrmForm::update( $child_form_id, array( 'parent_form_id' => $new_parent_form_id ) );
477 do_action( 'frm_update_child_form_parent_id', $child_form_id, $new_parent_form_id );
478 }
479 }
480 }
481
482 /**
483 * Import all fields for a form
484 *
485 * @since 2.0.13
486 *
487 * TODO: Cut down on params
488 *
489 * @param SimpleXMLElement $xml_fields
490 * @param int|string $form_id
491 * @param object $this_form
492 * @param array $form_fields
493 * @param array $imported
494 */
495 private static function import_xml_fields( $xml_fields, $form_id, $this_form, &$form_fields, &$imported ) {
496 $in_section = 0;
497 $keys_by_original_field_id = array();
498
499 foreach ( $xml_fields as $field ) {
500 $f = self::fill_field( $field, $form_id );
501
502 self::set_default_value( $f );
503 self::maybe_add_required( $f );
504 self::maybe_update_in_section_variable( $in_section, $f );
505 self::maybe_update_form_select( $f, $imported );
506 self::maybe_update_get_values_form_setting( $imported, $f );
507 self::migrate_placeholders( $f );
508
509 if ( ! empty( $this_form ) ) {
510 // check for field to edit by field id
511 if ( isset( $form_fields[ $f['id'] ] ) ) {
512 FrmField::update( $f['id'], $f );
513 ++$imported['updated']['fields'];
514
515 unset( $form_fields[ $f['id'] ] );
516
517 // Unset old field key.
518 if ( isset( $form_fields[ $f['field_key'] ] ) ) {
519 unset( $form_fields[ $f['field_key'] ] );
520 }
521 } elseif ( isset( $form_fields[ $f['field_key'] ] ) ) {
522 $keys_by_original_field_id[ $f['id'] ] = $f['field_key'];
523
524 $old_field_id = $f['id'];
525
526 // check for field to edit by field key
527 unset( $f['id'] );
528
529 FrmField::update( $form_fields[ $f['field_key'] ], $f );
530 ++$imported['updated']['fields'];
531
532 self::do_after_field_imported_action( $f, $form_fields, $old_field_id );
533
534 // Unset old field id.
535 unset( $form_fields[ $form_fields[ $f['field_key'] ] ] );
536
537 // Unset old field key.
538 unset( $form_fields[ $f['field_key'] ] );
539 } else {
540 // If no matching field id or key in this form, create the field.
541 self::create_imported_field( $f, $imported );
542 }//end if
543 } else {
544
545 self::create_imported_field( $f, $imported );
546 }//end if
547 }//end foreach
548
549 if ( $keys_by_original_field_id ) {
550 self::maybe_update_field_ids( $form_id, $keys_by_original_field_id );
551 }
552 }
553
554 /**
555 * @since 6.8.4
556 *
557 * @param array $field_array
558 *
559 * @return array
560 */
561 private static function update_field_options_with_defaults( $field_array ) {
562 $defaults = self::default_field_options( $field_array['type'] );
563 $field_array['field_options'] = array_merge( $defaults, $field_array['field_options'] );
564
565 return $field_array;
566 }
567
568 /**
569 * @since 6.8.4
570 *
571 * @param array $field_array
572 * @param array $form_fields
573 * @param int $old_field_id
574 *
575 * @return void
576 */
577 private static function do_after_field_imported_action( $field_array, $form_fields, $old_field_id ) {
578 // Assign field array the update field's ID.
579 $field_array['id'] = $form_fields[ $field_array['field_key'] ];
580
581 /**
582 * Fires when an existing field is imported.
583 *
584 * @since 6.8.4
585 *
586 * @param array $field_array
587 * @param int $field_id
588 * @param int $old_field_id
589 */
590 do_action( 'frm_after_existing_field_is_imported', $field_array, $form_fields[ $field_array['field_key'] ], $old_field_id );
591 }
592
593 /**
594 * @param object $field
595 * @param int|string $form_id
596 *
597 * @return array
598 */
599 private static function fill_field( $field, $form_id ) {
600 return array(
601 'id' => (int) $field->id,
602 'field_key' => (string) $field->field_key,
603 'name' => (string) $field->name,
604 'description' => (string) $field->description,
605 'type' => (string) $field->type,
606 'default_value' => FrmAppHelper::maybe_json_decode( (string) $field->default_value ),
607 'field_order' => (int) $field->field_order,
608 'form_id' => (int) $form_id,
609 'required' => (int) $field->required,
610 'options' => FrmAppHelper::maybe_json_decode( (string) $field->options ),
611 'field_options' => FrmAppHelper::maybe_json_decode( (string) $field->field_options ),
612 );
613 }
614
615 /**
616 * @since 4.06
617 *
618 * @param array $f
619 *
620 * @return void
621 */
622 private static function set_default_value( &$f ) {
623 $has_default = array(
624 'text',
625 'email',
626 'url',
627 'textarea',
628 'number',
629 'phone',
630 'date',
631 'hidden',
632 'password',
633 'tag',
634 );
635
636 if ( is_array( $f['default_value'] ) && in_array( $f['type'], $has_default, true ) ) {
637 if ( count( $f['default_value'] ) === 1 ) {
638 $f['default_value'] = '[' . reset( $f['default_value'] ) . ']';
639 } else {
640 $f['default_value'] = reset( $f['default_value'] );
641 }
642 }
643 }
644
645 /**
646 * Make sure the required indicator is set.
647 *
648 * @since 4.05
649 *
650 * @param array $f
651 *
652 * @return void
653 */
654 private static function maybe_add_required( &$f ) {
655 if ( $f['required'] && ! isset( $f['field_options']['required_indicator'] ) ) {
656 $f['field_options']['required_indicator'] = '*';
657 }
658 }
659
660 /**
661 * Update the current in_section value at the beginning of the field loop
662 *
663 * @since 2.0.25
664 *
665 * @param int $in_section
666 * @param array $f
667 *
668 * @return void
669 */
670 private static function maybe_update_in_section_variable( &$in_section, &$f ) {
671 // If we're at the end of a section, switch $in_section is 0
672 if ( in_array( $f['type'], array( 'end_divider', 'break', 'form' ) ) ) {
673 $in_section = 0;
674 }
675
676 // Update the current field's in_section value
677 if ( ! isset( $f['field_options']['in_section'] ) ) {
678 $f['field_options']['in_section'] = $in_section;
679 }
680
681 // If we're starting a new section, switch $in_section to ID of divider
682 if ( $f['type'] === 'divider' ) {
683 $in_section = $f['id'];
684 }
685 }
686
687 /**
688 * Switch the form_select on a repeating field or embedded form if it needs to be switched
689 *
690 * @since 2.0.16
691 *
692 * @param array $f
693 * @param array $imported
694 */
695 private static function maybe_update_form_select( &$f, $imported ) {
696 if ( ! isset( $imported['forms'] ) ) {
697 return;
698 }
699
700 if ( $f['type'] === 'form' || ( $f['type'] === 'divider' && FrmField::is_option_true( $f['field_options'], 'repeat' ) ) ) {
701 if ( FrmField::is_option_true( $f['field_options'], 'form_select' ) ) {
702 $form_select = (int) $f['field_options']['form_select'];
703
704 if ( isset( $imported['forms'][ $form_select ] ) ) {
705 $f['field_options']['form_select'] = $imported['forms'][ $form_select ];
706 }
707 }
708 }
709 }
710
711 /**
712 * Update the get_values_form setting if the form was imported
713 *
714 * @since 2.01.0
715 *
716 * @param array $imported
717 * @param array $f
718 *
719 * @return void
720 */
721 private static function maybe_update_get_values_form_setting( $imported, &$f ) {
722 if ( ! isset( $imported['forms'] ) ) {
723 return;
724 }
725
726 if ( FrmField::is_option_true_in_array( $f['field_options'], 'get_values_form' ) ) {
727 $old_form = $f['field_options']['get_values_form'];
728
729 if ( isset( $imported['forms'][ $old_form ] ) ) {
730 $f['field_options']['get_values_form'] = $imported['forms'][ $old_form ];
731 }
732 }
733 }
734
735 /**
736 * @since 4.0
737 *
738 * @param array $f
739 *
740 * @return void
741 */
742 private static function migrate_placeholders( &$f ) {
743 $update_values = self::migrate_field_placeholder( $f, 'clear_on_focus' );
744
745 foreach ( $update_values as $k => $v ) {
746 $f[ $k ] = $v;
747 }
748
749 $update_values = self::migrate_field_placeholder( $f, 'default_blank' );
750
751 foreach ( $update_values as $k => $v ) {
752 $f[ $k ] = $v;
753 }
754 }
755
756 /**
757 * Move clear_on_focus or default_blank to placeholder.
758 * Also called during database migration in FrmMigrate.
759 *
760 * @since 4.0
761 *
762 * @param array|object $field
763 * @param string $type
764 *
765 * @return array
766 */
767 public static function migrate_field_placeholder( $field, $type ) {
768 $field = (array) $field;
769 $field_options = $field['field_options'];
770
771 if ( empty( $field_options[ $type ] ) || empty( $field['default_value'] ) ) {
772 return array();
773 }
774
775 $field_options['placeholder'] = is_array( $field['default_value'] ) ? reset( $field['default_value'] ) : $field['default_value'];
776 unset( $field_options['default_blank'], $field_options['clear_on_focus'] );
777
778 $changes = array(
779 'field_options' => $field_options,
780 'default_value' => '',
781 );
782
783 // If a dropdown placeholder was used, remove the option so it won't be included twice.
784 $options = $field['options'];
785
786 if ( $type === 'default_blank' && is_array( $options ) ) {
787 $default_value = $field['default_value'];
788
789 if ( is_array( $default_value ) ) {
790 $default_value = reset( $default_value );
791 }
792
793 foreach ( $options as $opt_key => $opt ) {
794 if ( is_array( $opt ) ) {
795 $opt = $opt['value'] ?? $opt['label'] ?? reset( $opt );
796 }
797
798 if ( $opt == $default_value ) {
799 unset( $options[ $opt_key ] );
800 break;
801 }
802 }
803 $changes['options'] = $options;
804 }
805
806 return $changes;
807 }
808
809 /**
810 * Create an imported field
811 *
812 * @since 2.0.25
813 *
814 * @param array $f
815 * @param array $imported
816 *
817 * @return void
818 */
819 private static function create_imported_field( $f, &$imported ) {
820 $f = self::update_field_options_with_defaults( $f );
821
822 if ( is_callable( 'FrmProFileImport::import_attachment' ) ) {
823 $f = self::maybe_import_images_for_options( $f );
824 }
825
826 $new_id = FrmField::create( $f );
827
828 if ( $new_id != false ) {
829 ++$imported['imported']['fields'];
830 do_action( 'frm_after_field_is_imported', $f, $new_id );
831 }
832 }
833
834 /**
835 * Import images for radio buttons and checkboxes from image src if available.
836 *
837 * @since 5.5.1
838 *
839 * @param array $field
840 *
841 * @return array
842 */
843 private static function maybe_import_images_for_options( $field ) {
844 if ( empty( $field['options'] ) || ! is_array( $field['options'] ) ) {
845 return $field;
846 }
847
848 foreach ( $field['options'] as $key => $option ) {
849 if ( ! is_array( $option ) || empty( $option['src'] ) ) {
850 continue;
851 }
852
853 $field_object = (object) $field;
854 // Fake the file type as FrmProImport::import_attachment checks for file type.
855 $field_object->type = 'file';
856
857 $image_id = FrmProFileImport::import_attachment( $option['src'], $field_object );
858 // Remove the src from options as it isn't required after import.
859 unset( $field['options'][ $key ]['src'] );
860
861 if ( is_numeric( $image_id ) ) {
862 $field['options'][ $key ]['image'] = $image_id;
863 }
864 }
865
866 return $field;
867 }
868
869 /**
870 * Fix field ids for fields that already exist prior to import.
871 *
872 * @since 4.07
873 *
874 * @param int $form_id
875 * @param array $keys_by_original_field_id
876 *
877 * @return void
878 */
879 protected static function maybe_update_field_ids( $form_id, $keys_by_original_field_id ) {
880 global $frm_duplicate_ids;
881
882 $former_duplicate_ids = $frm_duplicate_ids;
883 $where = array(
884 array(
885 'or' => 1,
886 'fi.form_id' => $form_id,
887 'fr.parent_form_id' => $form_id,
888 ),
889 );
890 $fields = FrmField::getAll( $where, 'field_order' );
891 $field_id_by_key = wp_list_pluck( $fields, 'id', 'field_key' );
892
893 foreach ( $fields as $field ) {
894 $before = (array) clone $field;
895 $field = (array) $field;
896 $frm_duplicate_ids = $keys_by_original_field_id;
897 $after = FrmFieldsHelper::switch_field_ids( $field );
898
899 if ( $before['field_options'] !== $after['field_options'] ) {
900 $frm_duplicate_ids = $field_id_by_key;
901 $after = FrmFieldsHelper::switch_field_ids( $after );
902
903 if ( $before['field_options'] !== $after['field_options'] ) {
904 FrmField::update( $field['id'], array( 'field_options' => $after['field_options'] ) );
905 }
906 }
907 }
908
909 $frm_duplicate_ids = $former_duplicate_ids;
910 }
911
912 /**
913 * Updates the custom style setting on import
914 * Convert the post slug to an ID
915 *
916 * @since 2.0.19
917 *
918 * @param array $form
919 *
920 * @return void
921 */
922 private static function update_custom_style_setting_on_import( &$form ) {
923 if ( ! isset( $form['options']['custom_style'] ) ) {
924 return;
925 }
926
927 if ( is_numeric( $form['options']['custom_style'] ) && 1 === intval( $form['options']['custom_style'] ) ) {
928 // Set to default
929 $form['options']['custom_style'] = 1;
930 } else {
931 // Replace the style name with the style ID on import
932 global $wpdb;
933 $table = $wpdb->prefix . 'posts';
934 $where = array(
935 'post_name' => $form['options']['custom_style'],
936 'post_type' => 'frm_styles',
937 );
938 $select = 'ID';
939 $style_id = FrmDb::get_var( $table, $where, $select );
940
941 if ( $style_id ) {
942 $form['options']['custom_style'] = $style_id;
943 } else {
944 // save the old style to maybe update after styles import
945 $form['options']['old_style'] = $form['options']['custom_style'];
946
947 // Set to default
948 $form['options']['custom_style'] = 1;
949 }
950 }//end if
951 }
952
953 /**
954 * After styles are imported, check for any forms that were linked
955 * and link them back up.
956 *
957 * @since 2.2.7
958 *
959 * @param int|string $form_id
960 *
961 * @return void
962 */
963 private static function update_custom_style_setting_after_import( $form_id ) {
964 $form = FrmForm::getOne( $form_id );
965
966 if ( $form && isset( $form->options['old_style'] ) ) {
967 $form = (array) $form;
968 $saved_style = $form['options']['custom_style'];
969 $form['options']['custom_style'] = $form['options']['old_style'];
970 self::update_custom_style_setting_on_import( $form );
971 $has_changed = ( $form['options']['custom_style'] != $saved_style && $form['options']['custom_style'] != $form['options']['old_style'] );
972
973 if ( $has_changed ) {
974 FrmForm::update( $form['id'], $form );
975 }
976 }
977 }
978
979 /**
980 * Handle import for all posts. This includes views, styles, pages, and form actions.
981 *
982 * @param SimpleXMLElement $views
983 * @param array $imported
984 *
985 * @return array
986 */
987 public static function import_xml_views( $views, $imported ) {
988 $imported['posts'] = array();
989 $form_action_type = FrmFormActionsController::$action_post_type;
990
991 $post_types = array(
992 'frm_display' => 'views',
993 $form_action_type => 'actions',
994 'frm_styles' => 'styles',
995 );
996
997 $view_ids = array();
998 $posts_with_shortcodes = array();
999
1000 foreach ( $views as $item ) {
1001 $post = array(
1002 'post_title' => (string) $item->title,
1003 'post_name' => (string) $item->post_name,
1004 'post_type' => (string) $item->post_type,
1005 'post_password' => (string) $item->post_password,
1006 'guid' => (string) $item->guid,
1007 'post_status' => (string) $item->status,
1008 'post_author' => FrmAppHelper::get_user_id_param( (string) $item->post_author ),
1009 'post_id' => (int) $item->post_id,
1010 'post_parent' => (int) $item->post_parent,
1011 'menu_order' => (int) $item->menu_order,
1012 'post_content' => FrmFieldsHelper::switch_field_ids( (string) $item->content ),
1013 'post_excerpt' => FrmFieldsHelper::switch_field_ids( (string) $item->excerpt ),
1014 'is_sticky' => (string) $item->is_sticky,
1015 'comment_status' => (string) $item->comment_status,
1016 'post_date' => (string) $item->post_date,
1017 'post_date_gmt' => (string) $item->post_date_gmt,
1018 'ping_status' => (string) $item->ping_status,
1019 'postmeta' => array(),
1020 'layout' => array(),
1021 'tax_input' => array(),
1022 );
1023
1024 $post['post_content'] = self::switch_form_ids( $post['post_content'], $imported['forms'] );
1025
1026 // Fix issue with line breaks appearing in descriptions as "rn".
1027 if ( $post['post_type'] === $form_action_type ) {
1028 $post['post_content'] = str_replace( '\\\\r\\\\n', '\\r\\n', $post['post_content'] );
1029 }
1030
1031 $old_id = $post['post_id'];
1032 self::populate_post( $post, $item, $imported );
1033
1034 unset( $item );
1035
1036 $post_id = false;
1037
1038 if ( $post['post_type'] === $form_action_type ) {
1039 $action_control = FrmFormActionsController::get_form_actions( $post['post_excerpt'] );
1040
1041 if ( $action_control && is_object( $action_control ) && isset( $imported['form_status'] ) ) {
1042 $post_id = $action_control->maybe_create_action( $post, $imported['form_status'] );
1043 }
1044 unset( $action_control );
1045 } elseif ( $post['post_type'] === 'frm_styles' ) {
1046 // Properly encode post content before inserting the post
1047 $post['post_content'] = FrmAppHelper::maybe_json_decode( $post['post_content'] );
1048 $post['post_content'] = FrmAppHelper::prepare_and_encode( $post['post_content'] );
1049 // Store template data additionally to a postmeta that can be used to revert the style to default template style.
1050 $post['postmeta']['frm_style_default'] = $post['post_content'];
1051
1052 // Create/update post now
1053 $post_id = wp_insert_post( $post );
1054 } else {
1055 if ( $post['post_type'] === 'frm_display' ) {
1056 $post['post_content'] = self::maybe_prepare_json_view_content( $post['post_content'] );
1057 } elseif ( 'page' === $post['post_type'] && isset( $imported['posts'][ $post['post_parent'] ] ) ) {
1058 $post['post_parent'] = $imported['posts'][ $post['post_parent'] ];
1059 }
1060 // Create/update post now
1061 $post_id = wp_insert_post( $post );
1062 }//end if
1063
1064 if ( ! is_numeric( $post_id ) ) {
1065 continue;
1066 }
1067
1068 if ( false !== strpos( $post['post_content'], '[display-frm-data' ) || false !== strpos( $post['post_content'], '[formidable' ) ) {
1069 $posts_with_shortcodes[ $post_id ] = $post;
1070 }
1071
1072 self::update_postmeta( $post, $post_id );
1073 self::update_layout( $post, $post_id );
1074
1075 $this_type = 'posts';
1076
1077 if ( isset( $post_types[ $post['post_type'] ] ) ) {
1078 $this_type = $post_types[ $post['post_type'] ];
1079 }
1080
1081 if ( isset( $post['ID'] ) && $post_id == $post['ID'] ) {
1082 ++$imported['updated'][ $this_type ];
1083 } else {
1084 ++$imported['imported'][ $this_type ];
1085 }
1086
1087 $imported['posts'][ $old_id ] = $post_id;
1088
1089 if ( $post['post_type'] === 'frm_display' ) {
1090 $view_ids[ $old_id ] = $post_id;
1091 }
1092
1093 do_action( 'frm_after_import_view', $post_id, $post );
1094
1095 unset( $post );
1096 }//end foreach
1097
1098 if ( $posts_with_shortcodes && $view_ids ) {
1099 self::maybe_switch_view_ids_after_importing_posts( $posts_with_shortcodes, $view_ids );
1100 }
1101 unset( $posts_with_shortcodes, $view_ids );
1102
1103 if ( ! empty( $imported['forms'] ) ) {
1104 // clear imported forms style cache to make sure the new styles are applied to the forms
1105 self::clear_forms_style_caches( $imported['forms'] );
1106 }
1107
1108 self::maybe_update_stylesheet( $imported );
1109
1110 flush_rewrite_rules();
1111
1112 return $imported;
1113 }
1114
1115 /**
1116 * Clears styles from cache for imported forms
1117 *
1118 * @param array $imported_forms
1119 *
1120 * @return void
1121 */
1122 private static function clear_forms_style_caches( $imported_forms ) {
1123 $where = array(
1124 'id' => $imported_forms,
1125 'options LIKE' => '"old_style"',
1126 );
1127 $forms = FrmDb::get_results( 'frm_forms', $where );
1128
1129 foreach ( $forms as $form ) {
1130 FrmAppHelper::unserialize_or_decode( $form->options );
1131
1132 if ( ! $form->options ) {
1133 continue;
1134 }
1135
1136 $where = array(
1137 'post_name' => $form->options['old_style'],
1138 'post_type' => FrmStylesController::$post_type,
1139 );
1140
1141 $select = 'ID';
1142
1143 $cache_key = FrmDb::generate_cache_key( $where, array( 'limit' => 1 ), $select, 'var' );
1144 FrmDb::delete_cache_and_transient( $cache_key, 'post' );
1145 }
1146 }
1147
1148 /**
1149 * Replace old form ids with new ones in a string.
1150 *
1151 * @param string $string
1152 * @param array<int> $form_ids new form ids indexed by old form id.
1153 *
1154 * @return string
1155 */
1156 private static function switch_form_ids( $string, $form_ids ) {
1157 if ( false === strpos( $string, '[formidable' ) ) {
1158 // Skip string replacing if there are no form shortcodes in string.
1159 return $string;
1160 }
1161
1162 foreach ( $form_ids as $old_id => $new_id ) {
1163 $string = str_replace(
1164 array(
1165 '[formidable id="' . $old_id . '"',
1166 '[formidable id=' . $old_id . ']',
1167 '[formidable id=' . $old_id . ' ',
1168 '"formId":"' . $old_id . '"',
1169 ),
1170 array(
1171 '[formidable id="' . $new_id . '"',
1172 '[formidable id=' . $new_id . ']',
1173 '[formidable id=' . $new_id . ' ',
1174 '"formId":"' . $new_id . '"',
1175 ),
1176 $string
1177 );
1178 }
1179
1180 return $string;
1181 }
1182
1183 /**
1184 * @param array<array> $posts_with_shortcodes indexed by current post id.
1185 * @param array<int> $view_ids new view ids indexed by old view id.
1186 *
1187 * @return void
1188 */
1189 private static function maybe_switch_view_ids_after_importing_posts( $posts_with_shortcodes, $view_ids ) {
1190 foreach ( $posts_with_shortcodes as $imported_post_id => $post ) {
1191 $post_content = self::switch_view_ids( $post['post_content'], $view_ids );
1192
1193 if ( $post_content === $post['post_content'] ) {
1194 continue;
1195 }
1196
1197 wp_update_post(
1198 array(
1199 'ID' => $imported_post_id,
1200 'post_content' => $post_content,
1201 )
1202 );
1203 }
1204 }
1205
1206 /**
1207 * Replace old view ids with new ones in a string.
1208 *
1209 * @param string $string
1210 * @param array<int> $view_ids new view ids indexed by old view id.
1211 *
1212 * @return string
1213 */
1214 private static function switch_view_ids( $string, $view_ids ) {
1215 if ( false === strpos( $string, '[display-frm-data' ) ) {
1216 // Skip string replacing if there are no view shortcodes in string.
1217 return $string;
1218 }
1219
1220 foreach ( $view_ids as $old_id => $new_id ) {
1221 $string = str_replace(
1222 array(
1223 '[display-frm-data id="' . $old_id . '"',
1224 '[display-frm-data id=' . $old_id . ']',
1225 '[display-frm-data id=' . $old_id . ' ',
1226 '"viewId":"' . $old_id . '"',
1227 ),
1228 array(
1229 '[display-frm-data id="' . $new_id . '"',
1230 '[display-frm-data id=' . $new_id . ']',
1231 '[display-frm-data id=' . $new_id . ' ',
1232 '"viewId":"' . $new_id . '"',
1233 ),
1234 $string
1235 );
1236 unset( $old_id, $new_id );
1237 }
1238
1239 return $string;
1240 }
1241
1242 /**
1243 * @param string $content
1244 *
1245 * @return string
1246 */
1247 private static function maybe_prepare_json_view_content( $content ) {
1248 $maybe_decoded = FrmAppHelper::maybe_json_decode( $content );
1249
1250 if ( is_array( $maybe_decoded ) && isset( $maybe_decoded[0] ) && isset( $maybe_decoded[0]['box'] ) ) {
1251 return FrmAppHelper::prepare_and_encode( $maybe_decoded );
1252 }
1253 return $content;
1254 }
1255
1256 /**
1257 * @param array $post
1258 * @param object $item
1259 * @param array $imported
1260 *
1261 * @return void
1262 */
1263 private static function populate_post( &$post, $item, $imported ) {
1264 if ( isset( $item->attachment_url ) ) {
1265 $post['attachment_url'] = (string) $item->attachment_url;
1266 }
1267
1268 if ( $post['post_type'] == FrmFormActionsController::$action_post_type && isset( $imported['forms'][ (int) $post['menu_order'] ] ) ) {
1269 // update to new form id
1270 $post['menu_order'] = $imported['forms'][ (int) $post['menu_order'] ];
1271 }
1272
1273 // Don't allow default styles to take over a site's default style
1274 if ( 'frm_styles' == $post['post_type'] ) {
1275 $post['menu_order'] = 0;
1276 }
1277
1278 foreach ( $item->postmeta as $meta ) {
1279 self::populate_postmeta( $post, $meta, $imported );
1280 unset( $meta );
1281 }
1282
1283 foreach ( $item->layout as $layout ) {
1284 self::populate_layout( $post, $layout );
1285 unset( $layout );
1286 }
1287
1288 self::populate_taxonomies( $post, $item );
1289
1290 self::maybe_editing_post( $post );
1291 }
1292
1293 /**
1294 * @param array $post
1295 * @param stdClass $meta
1296 * @param array $imported
1297 */
1298 private static function populate_postmeta( &$post, $meta, $imported ) {
1299 global $frm_duplicate_ids;
1300
1301 $m = array(
1302 'key' => (string) $meta->meta_key,
1303 'value' => (string) $meta->meta_value,
1304 );
1305
1306 // Switch old form and field ids to new ones.
1307 if ( 'frm_form_id' === $m['key'] && isset( $imported['forms'][ (int) $m['value'] ] ) ) {
1308 $m['value'] = $imported['forms'][ (int) $m['value'] ];
1309 } else {
1310 $m['value'] = FrmAppHelper::maybe_json_decode( $m['value'] );
1311
1312 if ( ! empty( $frm_duplicate_ids ) ) {
1313 if ( 'frm_dyncontent' === $m['key'] ) {
1314 $m['value'] = self::maybe_prepare_json_view_content( $m['value'] );
1315 $m['value'] = FrmFieldsHelper::switch_field_ids( $m['value'] );
1316 } elseif ( 'frm_options' === $m['key'] ) {
1317
1318 foreach ( array( 'date_field_id', 'edate_field_id' ) as $setting_name ) {
1319 if ( isset( $m['value'][ $setting_name ] ) && is_numeric( $m['value'][ $setting_name ] ) && isset( $frm_duplicate_ids[ $m['value'][ $setting_name ] ] ) ) {
1320 $m['value'][ $setting_name ] = $frm_duplicate_ids[ $m['value'][ $setting_name ] ];
1321 }
1322 }
1323
1324 if ( ! empty( $m['value']['map_address_fields'] ) ) {
1325 foreach ( $m['value']['map_address_fields'] as $address_field_key => $address_field_id ) {
1326 if ( isset( $frm_duplicate_ids[ $address_field_id ] ) ) {
1327 $m['value']['map_address_fields'][ $address_field_key ] = $frm_duplicate_ids[ $address_field_id ];
1328 }
1329 }
1330 }
1331
1332 if ( ! empty( $m['value']['calendar_options'] ) ) {
1333 foreach ( $m['value']['calendar_options'] as $calendar_option_group_key => $calendar_option ) {
1334 if ( isset( $frm_duplicate_ids[ $calendar_option['value'] ] ) ) {
1335 $m['value']['calendar_options'][ $calendar_option_group_key ]['value'] = $frm_duplicate_ids[ $calendar_option['value'] ];
1336 }
1337 }
1338 }
1339
1340 if ( ! empty( $m['value']['timeline_options'] ) ) {
1341 foreach ( $m['value']['timeline_options'] as $timeline_option_group_key => $timeline_group_option ) {
1342 foreach ( $timeline_group_option as $timeline_option_key => $timeline_option ) {
1343 if ( isset( $frm_duplicate_ids[ $timeline_option ] ) ) {
1344 $m['value']['timeline_options'][ $timeline_option_group_key ][ $timeline_option_key ] = $frm_duplicate_ids[ $timeline_option ];
1345 }
1346 }
1347 }
1348 }
1349
1350 $check_dup_array = array();
1351
1352 if ( ! empty( $m['value']['order_by'] ) ) {
1353 if ( is_numeric( $m['value']['order_by'] ) && isset( $frm_duplicate_ids[ $m['value']['order_by'] ] ) ) {
1354 $m['value']['order_by'] = $frm_duplicate_ids[ $m['value']['order_by'] ];
1355 } elseif ( is_array( $m['value']['order_by'] ) ) {
1356 $check_dup_array[] = 'order_by';
1357 }
1358 }
1359
1360 if ( ! empty( $m['value']['where'] ) ) {
1361 $check_dup_array[] = 'where';
1362 }
1363
1364 foreach ( $check_dup_array as $check_k ) {
1365 foreach ( (array) $m['value'][ $check_k ] as $mk => $mv ) {
1366 if ( isset( $frm_duplicate_ids[ $mv ] ) ) {
1367 $m['value'][ $check_k ][ $mk ] = $frm_duplicate_ids[ $mv ];
1368 }
1369 unset( $mk, $mv );
1370 }
1371 }
1372 }//end if
1373 }//end if
1374 }//end if
1375
1376 if ( ! is_array( $m['value'] ) ) {
1377 $m['value'] = FrmAppHelper::maybe_json_decode( $m['value'] );
1378 }
1379
1380 $post['postmeta'][ (string) $meta->meta_key ] = $m['value'];
1381 }
1382
1383 /**
1384 * @param array $post
1385 * @param object $layout
1386 *
1387 * @return void
1388 */
1389 private static function populate_layout( &$post, $layout ) {
1390 $post['layout'][ (string) $layout->type ] = (string) $layout->data;
1391 }
1392
1393 /**
1394 * Add terms to post
1395 *
1396 * @param array $post By reference.
1397 * @param object $item The XML object data.
1398 */
1399 private static function populate_taxonomies( &$post, $item ) {
1400 foreach ( $item->category as $c ) {
1401 $att = $c->attributes();
1402
1403 if ( ! isset( $att['nicename'] ) ) {
1404 continue;
1405 }
1406
1407 $taxonomy = (string) $att['domain'];
1408
1409 if ( is_taxonomy_hierarchical( $taxonomy ) ) {
1410 $name = (string) $att['nicename'];
1411 $h_term = get_term_by( 'slug', $name, $taxonomy );
1412
1413 if ( $h_term ) {
1414 $name = $h_term->term_id;
1415 }
1416 unset( $h_term );
1417 } else {
1418 $name = (string) $c;
1419 }
1420
1421 if ( ! isset( $post['tax_input'][ $taxonomy ] ) ) {
1422 $post['tax_input'][ $taxonomy ] = array();
1423 }
1424
1425 $post['tax_input'][ $taxonomy ][] = $name;
1426 unset( $name );
1427 }//end foreach
1428 }
1429
1430 /**
1431 * Edit post if the key and created time match.
1432 *
1433 * @param array $post By reference.
1434 *
1435 * @return void
1436 */
1437 private static function maybe_editing_post( &$post ) {
1438 $match_by = array(
1439 'post_type' => $post['post_type'],
1440 'name' => $post['post_name'],
1441 'post_status' => $post['post_status'],
1442 'posts_per_page' => 1,
1443 );
1444
1445 if ( in_array( $post['post_status'], array( 'trash', 'draft' ), true ) ) {
1446 $match_by['include'] = $post['post_id'];
1447 unset( $match_by['name'] );
1448 }
1449
1450 $editing = get_posts( $match_by );
1451
1452 if ( ! empty( $editing ) && current( $editing )->post_date == $post['post_date'] ) {
1453 // set the id of the post to edit
1454 $post['ID'] = current( $editing )->ID;
1455 }
1456 }
1457
1458 /**
1459 * @param array $post
1460 * @param int $post_id
1461 *
1462 * @return void
1463 */
1464 private static function update_postmeta( &$post, $post_id ) {
1465 foreach ( $post['postmeta'] as $k => $v ) {
1466 switch ( $k ) {
1467 case '_edit_last':
1468 $v = FrmAppHelper::get_user_id_param( $v );
1469 break;
1470
1471 case '_thumbnail_id':
1472 if ( FrmAppHelper::pro_is_installed() ) {
1473 // Change the attachment ID.
1474 $field_obj = FrmFieldFactory::get_field_type( 'file' );
1475 $v = $field_obj->get_file_id( $v );
1476 }
1477 break;
1478
1479 case 'frm_dyncontent':
1480 if ( is_array( $v ) ) {
1481 $v = json_encode( $v );
1482 }
1483 break;
1484
1485 case 'frm_param':
1486 add_rewrite_endpoint( $v, EP_PERMALINK | EP_PAGES );
1487 break;
1488 }//end switch
1489
1490 update_post_meta( $post_id, $k, $v );
1491 unset( $k, $v );
1492 }//end foreach
1493 }
1494
1495 /**
1496 * @param array $post
1497 * @param int $post_id
1498 */
1499 private static function update_layout( &$post, $post_id ) {
1500 if ( is_callable( 'FrmViewsLayout::maybe_create_layouts_for_view' ) ) {
1501 $listing_layout = ! empty( $post['layout']['listing'] ) ? json_decode( $post['layout']['listing'], true ) : array();
1502 $detail_layout = ! empty( $post['layout']['detail'] ) ? json_decode( $post['layout']['detail'], true ) : array();
1503
1504 if ( $listing_layout || $detail_layout ) {
1505 FrmViewsLayout::maybe_create_layouts_for_view( $post_id, $listing_layout, $detail_layout );
1506 }
1507 }
1508 }
1509
1510 /**
1511 * @param array $imported
1512 *
1513 * @return void
1514 */
1515 private static function maybe_update_stylesheet( $imported ) {
1516 $new_styles = ! empty( $imported['imported']['styles'] );
1517 $updated_styles = ! empty( $imported['updated']['styles'] );
1518
1519 if ( $new_styles || $updated_styles ) {
1520 if ( is_admin() && function_exists( 'get_filesystem_method' ) ) {
1521 $frm_style = new FrmStyle();
1522 $frm_style->update( 'default' );
1523 }
1524
1525 foreach ( $imported['forms'] as $form_id ) {
1526 self::update_custom_style_setting_after_import( $form_id );
1527 }
1528 }
1529 }
1530
1531 /**
1532 * @param mixed $result
1533 * @param string $message
1534 * @param array $errors
1535 */
1536 public static function parse_message( $result, &$message, &$errors ) {
1537 if ( is_wp_error( $result ) ) {
1538 $errors[] = $result->get_error_message();
1539
1540 // Remove the SimpleXML_parse_error from the WP_Error object to avoid
1541 // displaying duplicate error messages from $result->get_error_message()
1542 $error_codes = $result->get_error_codes();
1543 $error_details = array();
1544
1545 foreach ( $error_codes as $error_code ) {
1546 // Clone WP_Error data because WP_Error removes all error messages and data
1547 // associated with the specified error code when an item is removed.
1548 // Source: https://developer.wordpress.org/reference/classes/wp_error/remove/#source
1549 $error_details = $result->get_error_data( $error_code );
1550
1551 if ( $error_code === 'SimpleXML_parse_error' ) {
1552 $result->remove( $error_code );
1553 break;
1554 }
1555 }
1556
1557 if ( ! empty( $error_details ) ) {
1558 $errors[] = '<br />' . esc_html_x( 'Error details:', 'import xml message', 'formidable' ) . '<br />' . esc_html( print_r( $error_details, 1 ) );
1559 }
1560
1561 return;
1562 }//end if
1563
1564 if ( ! $result ) {
1565 return;
1566 }
1567
1568 if ( ! is_array( $result ) ) {
1569 $message = is_string( $result ) ? $result : htmlentities( print_r( $result, 1 ) );
1570 return;
1571 }
1572
1573 $t_strings = array(
1574 'imported' => __( 'Imported', 'formidable' ),
1575 'updated' => __( 'Updated', 'formidable' ),
1576 );
1577
1578 $message = '<ul>';
1579
1580 foreach ( $result as $type => $results ) {
1581 if ( ! isset( $t_strings[ $type ] ) ) {
1582 // only print imported and updated
1583 continue;
1584 }
1585
1586 $s_message = array();
1587
1588 foreach ( $results as $k => $m ) {
1589 self::item_count_message( $m, $k, $s_message );
1590 unset( $k, $m );
1591 }
1592
1593 if ( ! empty( $s_message ) ) {
1594 $message .= '<li><strong>' . $t_strings[ $type ] . ':</strong> ';
1595 $message .= implode( ', ', $s_message );
1596 $message .= '</li>';
1597 }
1598 }
1599
1600 if ( $message === '<ul>' ) {
1601 $message = '';
1602 $errors[] = __( 'Nothing was imported or updated', 'formidable' );
1603 } else {
1604 self::add_form_link_to_message( $result, $message );
1605
1606 /**
1607 * @since 5.3
1608 *
1609 * @param string $message
1610 * @param array $result
1611 */
1612 $message = apply_filters( 'frm_xml_parsed_message', $message, $result );
1613 $message .= '</ul>';
1614 }
1615 }
1616
1617 /**
1618 * @param int $m
1619 * @param string $type
1620 * @param array<string> $s_message
1621 */
1622 public static function item_count_message( $m, $type, &$s_message ) {
1623 if ( ! $m ) {
1624 return;
1625 }
1626
1627 $strings = array(
1628 /* translators: %1$s: Number of items */
1629 'forms' => sprintf( _n( '%1$s Form', '%1$s Forms', $m, 'formidable' ), $m ),
1630 /* translators: %1$s: Number of items */
1631 'fields' => sprintf( _n( '%1$s Field', '%1$s Fields', $m, 'formidable' ), $m ),
1632 /* translators: %1$s: Number of items */
1633 'items' => sprintf( _n( '%1$s Entry', '%1$s Entries', $m, 'formidable' ), $m ),
1634 /* translators: %1$s: Number of items */
1635 'views' => sprintf( _n( '%1$s View', '%1$s Views', $m, 'formidable' ), $m ),
1636 /* translators: %1$s: Number of items */
1637 'posts' => sprintf( _n( '%1$s Page/Post', '%1$s Pages/Posts', $m, 'formidable' ), $m ),
1638 /* translators: %1$s: Number of items */
1639 'styles' => sprintf( _n( '%1$s Style', '%1$s Styles', $m, 'formidable' ), $m ),
1640 /* translators: %1$s: Number of items */
1641 'terms' => sprintf( _n( '%1$s Term', '%1$s Terms', $m, 'formidable' ), $m ),
1642 /* translators: %1$s: Number of items */
1643 'actions' => sprintf( _n( '%1$s Form Action', '%1$s Form Actions', $m, 'formidable' ), $m ),
1644 );
1645
1646 if ( isset( $strings[ $type ] ) ) {
1647 $s_message[] = $strings[ $type ];
1648 } else {
1649 $string = ' ' . $m . ' ' . ucfirst( $type );
1650
1651 /**
1652 * @since 5.3
1653 *
1654 * @param string $string Message string for imported item.
1655 * @param int $m Number of item that was imported.
1656 * }
1657 */
1658 $string = apply_filters( 'frm_xml_' . $type . '_count_message', $string, $m );
1659 $s_message[] = $string;
1660 }
1661 }
1662
1663 /**
1664 * If a single form was imported, include a link in the success message.
1665 *
1666 * @since 4.0
1667 *
1668 * @param array $result The response from the XML import.
1669 * @param string $message The response shown on the page after import.
1670 */
1671 private static function add_form_link_to_message( $result, &$message ) {
1672 $total_forms = $result['imported']['forms'] + $result['updated']['forms'];
1673
1674 if ( $total_forms > 1 ) {
1675 return;
1676 }
1677
1678 $primary_form = reset( $result['forms'] );
1679
1680 if ( ! empty( $primary_form ) ) {
1681 $primary_form = FrmForm::getOne( $primary_form );
1682 $form_id = empty( $primary_form->parent_form_id ) ? $primary_form->id : $primary_form->parent_form_id;
1683
1684 $message .= '<li><a href="' . esc_url( FrmForm::get_edit_link( $form_id ) ) . '">' . esc_html__( 'Go to imported form', 'formidable' ) . '</a></li>';
1685 }
1686 }
1687
1688 /**
1689 * Prepare the form options for export
1690 *
1691 * @since 2.0.19
1692 *
1693 * @param string $options
1694 *
1695 * @return string
1696 */
1697 public static function prepare_form_options_for_export( $options ) {
1698 FrmAppHelper::unserialize_or_decode( $options );
1699 // Change custom_style to the post_name instead of ID (1 may be a string)
1700 $not_default = isset( $options['custom_style'] ) && 1 != $options['custom_style'];
1701
1702 if ( $not_default ) {
1703 global $wpdb;
1704 $table = $wpdb->prefix . 'posts';
1705 $where = array( 'ID' => $options['custom_style'] );
1706 $select = 'post_name';
1707
1708 $style_name = FrmDb::get_var( $table, $where, $select );
1709
1710 if ( $style_name ) {
1711 $options['custom_style'] = $style_name;
1712 } else {
1713 $options['custom_style'] = 1;
1714 }
1715 }
1716 self::remove_default_form_options( $options );
1717 $options = serialize( $options );
1718
1719 return self::cdata( $options );
1720 }
1721
1722 /**
1723 * If the saved value is the same as the default, remove it from the export
1724 * This keeps file size down and prevents overriding global settings after import
1725 *
1726 * @since 3.06
1727 *
1728 * @param array $options By reference.
1729 *
1730 * @return void
1731 */
1732 private static function remove_default_form_options( &$options ) {
1733 $defaults = FrmFormsHelper::get_default_opts();
1734
1735 if ( is_callable( 'FrmProFormsHelper::get_default_opts' ) ) {
1736 $defaults += FrmProFormsHelper::get_default_opts();
1737 }
1738 self::remove_defaults( $defaults, $options );
1739 }
1740
1741 /**
1742 * Remove extra settings from field to keep file size down
1743 *
1744 * @since 3.06
1745 *
1746 * @param object $field
1747 *
1748 * @return void
1749 */
1750 public static function prepare_field_for_export( &$field ) {
1751 self::remove_default_field_options( $field );
1752 self::add_image_src_to_image_options( $field );
1753 }
1754
1755 /**
1756 * Remove defaults from field options too
1757 *
1758 * @since 3.06
1759 *
1760 * @param object $field
1761 *
1762 * @return void
1763 */
1764 private static function remove_default_field_options( &$field ) {
1765 $defaults = self::default_field_options( $field->type );
1766
1767 if ( empty( $defaults['blank'] ) ) {
1768 $global_settings = new FrmSettings();
1769 $global_defaults = $global_settings->default_options();
1770 $defaults['blank'] = $global_defaults['blank_msg'];
1771 }
1772
1773 $options = $field->field_options;
1774 FrmAppHelper::unserialize_or_decode( $options );
1775 self::remove_defaults( $defaults, $options );
1776 self::remove_default_html( 'custom_html', $defaults, $options );
1777
1778 // Get variations on the defaults.
1779 if ( isset( $options['invalid'] ) ) {
1780 $defaults = array(
1781 /* translators: %s: Field name */
1782 'invalid' => sprintf( __( '%s is invalid', 'formidable' ), $field->name ),
1783 );
1784 self::remove_defaults( $defaults, $options );
1785 }
1786
1787 $field->field_options = serialize( $options );
1788 }
1789
1790 /**
1791 * Add image "src" key to each image option so the image can be imported to another website.
1792 *
1793 * @since 5.5.1
1794 *
1795 * @param stdClass $field
1796 *
1797 * @return void
1798 */
1799 private static function add_image_src_to_image_options( $field ) {
1800 if ( empty( $field->options ) || false === strpos( $field->options, 'image' ) ) {
1801 return;
1802 }
1803
1804 $updated = false;
1805 $options = $field->options;
1806 FrmAppHelper::unserialize_or_decode( $options );
1807
1808 if ( ! $options || ! is_array( $options ) ) {
1809 return;
1810 }
1811
1812 foreach ( $options as $key => $option ) {
1813 if ( is_array( $option ) && ! empty( $option['image'] ) ) {
1814 $options[ $key ]['src'] = wp_get_attachment_url( $option['image'] );
1815 $updated = true;
1816 }
1817 }
1818
1819 if ( $updated ) {
1820 $field->options = maybe_serialize( $options );
1821 }
1822 }
1823
1824 /**
1825 * @since 3.06.03
1826 *
1827 * @param string $type
1828 *
1829 * @return array
1830 */
1831 private static function default_field_options( $type ) {
1832 $defaults = FrmFieldsHelper::get_default_field_options( $type );
1833
1834 if ( empty( $defaults['custom_html'] ) ) {
1835 $defaults['custom_html'] = FrmFieldsHelper::get_default_html( $type );
1836 }
1837 return $defaults;
1838 }
1839
1840 /**
1841 * Compare the default array to the saved values and
1842 * remove if they are the same
1843 *
1844 * @since 3.06
1845 *
1846 * @param array $defaults
1847 * @param array $saved
1848 *
1849 * @return void
1850 */
1851 private static function remove_defaults( $defaults, &$saved ) {
1852 foreach ( $saved as $key => $value ) {
1853 if ( isset( $defaults[ $key ] ) && $defaults[ $key ] === $value ) {
1854 unset( $saved[ $key ] );
1855 }
1856 }
1857 }
1858
1859 /**
1860 * The line endings may prevent html from being equal when it should
1861 *
1862 * @since 3.06
1863 *
1864 * @param string $html_name
1865 * @param array $defaults
1866 * @param array $options
1867 *
1868 * @return void
1869 */
1870 private static function remove_default_html( $html_name, $defaults, &$options ) {
1871 if ( ! isset( $options[ $html_name ] ) || ! isset( $defaults[ $html_name ] ) ) {
1872 return;
1873 }
1874
1875 $old_html = str_replace( "\r\n", "\n", $options[ $html_name ] );
1876 $default_html = $defaults[ $html_name ];
1877
1878 if ( $old_html == $default_html ) {
1879 unset( $options[ $html_name ] );
1880
1881 return;
1882 }
1883
1884 // Account for some of the older field default HTML.
1885 $default_html = str_replace( ' id="frm_desc_field_[key]"', '', $default_html );
1886
1887 if ( $old_html === $default_html ) {
1888 unset( $options[ $html_name ] );
1889 }
1890 }
1891
1892 /**
1893 * @param string $str
1894 *
1895 * @return string
1896 */
1897 public static function cdata( $str ) {
1898 FrmAppHelper::unserialize_or_decode( $str );
1899
1900 if ( is_array( $str ) ) {
1901 $str = json_encode( $str );
1902 } elseif ( FrmAppHelper::is_valid_utf8( $str ) === false ) {
1903 $str = FrmAppHelper::maybe_utf8_encode( $str );
1904 }
1905
1906 if ( is_numeric( $str ) ) {
1907 return $str;
1908 }
1909
1910 self::remove_invalid_characters_from_xml( $str );
1911
1912 // $str = ent2ncr(esc_html( $str));
1913 $str = '<![CDATA[' . str_replace( ']]>', ']]]]><![CDATA[>', $str ) . ']]>';
1914
1915 return $str;
1916 }
1917
1918 /**
1919 * Remove <US> character (unit separator) from exported strings
1920 *
1921 * @since 2.0.22
1922 *
1923 * @param string $str
1924 *
1925 * @return void
1926 */
1927 private static function remove_invalid_characters_from_xml( &$str ) {
1928 // Remove <US> character
1929 $str = str_replace( '\x1F', '', $str );
1930 }
1931
1932 /**
1933 * Migrate form settings to actions
1934 *
1935 * @param array $form_options
1936 * @param int $form_id
1937 * @param array $imported
1938 * @param bool $switch
1939 *
1940 * @return void
1941 */
1942 public static function migrate_form_settings_to_actions( $form_options, $form_id, &$imported = array(), $switch = false ) {
1943 // Get post type
1944 $post_type = FrmFormActionsController::$action_post_type;
1945
1946 // Set up imported index, if not set up yet
1947 if ( ! isset( $imported['imported']['actions'] ) ) {
1948 $imported['imported']['actions'] = 0;
1949 }
1950
1951 // Migrate post settings to action
1952 self::migrate_post_settings_to_action( $form_options, $form_id, $post_type, $imported, $switch );
1953
1954 // Migrate email settings to action
1955 self::migrate_email_settings_to_action( $form_options, $form_id, $post_type, $imported, $switch );
1956 }
1957
1958 /**
1959 * Migrate post settings to form action
1960 *
1961 * @param array $form_options
1962 * @param int $form_id
1963 * @param string $post_type
1964 * @param array $imported
1965 * @param bool $switch
1966 */
1967 private static function migrate_post_settings_to_action( $form_options, $form_id, $post_type, &$imported, $switch ) {
1968 if ( ! isset( $form_options['create_post'] ) || ! $form_options['create_post'] ) {
1969 return;
1970 }
1971
1972 $new_action = array(
1973 'post_type' => $post_type,
1974 'post_excerpt' => 'wppost',
1975 'post_title' => __( 'Create Posts', 'formidable' ),
1976 'menu_order' => $form_id,
1977 'post_status' => 'publish',
1978 'post_content' => array(),
1979 'post_name' => $form_id . '_wppost_1',
1980 );
1981
1982 $post_settings = array(
1983 'post_type',
1984 'post_category',
1985 'post_content',
1986 'post_excerpt',
1987 'post_title',
1988 'post_name',
1989 'post_date',
1990 'post_status',
1991 'post_custom_fields',
1992 'post_password',
1993 'post_parent',
1994 );
1995
1996 foreach ( $post_settings as $post_setting ) {
1997 if ( isset( $form_options[ $post_setting ] ) ) {
1998 $new_action['post_content'][ $post_setting ] = $form_options[ $post_setting ];
1999 }
2000 unset( $post_setting );
2001 }
2002
2003 $new_action['event'] = array( 'create', 'update' );
2004
2005 if ( $switch ) {
2006 // Fields with string or int saved.
2007 $basic_fields = array(
2008 'post_title',
2009 'post_content',
2010 'post_excerpt',
2011 'post_password',
2012 'post_date',
2013 'post_status',
2014 'post_parent',
2015 );
2016
2017 // Fields with arrays saved.
2018 $array_fields = array( 'post_category', 'post_custom_fields' );
2019
2020 $new_action['post_content'] = self::switch_action_field_ids( $new_action['post_content'], $basic_fields, $array_fields );
2021 }
2022 $new_action['post_content'] = json_encode( $new_action['post_content'] );
2023
2024 $exists = get_posts(
2025 array(
2026 'name' => $new_action['post_name'],
2027 'post_type' => $new_action['post_type'],
2028 'post_status' => $new_action['post_status'],
2029 'numberposts' => 1,
2030 )
2031 );
2032
2033 if ( ! $exists ) {
2034 // this isn't an email, but we need to use a class that will always be included
2035 FrmDb::save_json_post( $new_action );
2036 ++$imported['imported']['actions'];
2037 }
2038 }
2039
2040 /**
2041 * Switch old field IDs for new field IDs in emails and post
2042 *
2043 * @since 2.0
2044 *
2045 * @param array $post_content Check for old field IDs.
2046 * @param array $basic_fields Fields with string or int saved.
2047 * @param array $array_fields Fields with arrays saved.
2048 *
2049 * @return string $post_content - new field IDs
2050 */
2051 private static function switch_action_field_ids( $post_content, $basic_fields, $array_fields = array() ) {
2052 global $frm_duplicate_ids;
2053
2054 // If there aren't IDs that were switched, end now
2055 if ( ! $frm_duplicate_ids ) {
2056 return;
2057 }
2058
2059 // Get old IDs
2060 $old = array_keys( $frm_duplicate_ids );
2061
2062 // Get new IDs
2063 $new = array_values( $frm_duplicate_ids );
2064
2065 // Do a str_replace with each item to set the new IDs
2066 foreach ( $post_content as $key => $setting ) {
2067 if ( ! is_array( $setting ) && in_array( $key, $basic_fields ) ) {
2068 // Replace old IDs with new IDs
2069 $post_content[ $key ] = str_replace( $old, $new, $setting );
2070 } elseif ( is_array( $setting ) && in_array( $key, $array_fields ) ) {
2071 foreach ( $setting as $k => $val ) {
2072 // Replace old IDs with new IDs
2073 $post_content[ $key ][ $k ] = str_replace( $old, $new, $val );
2074 }
2075 }
2076 unset( $key, $setting );
2077 }
2078
2079 return $post_content;
2080 }
2081
2082 /**
2083 * Migrate email settings to action
2084 *
2085 * @param array $form_options
2086 * @param int $form_id
2087 * @param string $post_type
2088 * @param array $imported
2089 * @param bool $switch
2090 *
2091 * @return void
2092 */
2093 private static function migrate_email_settings_to_action( $form_options, $form_id, $post_type, &$imported, $switch ) {
2094 // No old notifications or autoresponders to carry over
2095 if ( ! isset( $form_options['auto_responder'] ) && ! isset( $form_options['notification'] ) && ! isset( $form_options['email_to'] ) ) {
2096 return;
2097 }
2098
2099 // Initialize notifications array
2100 $notifications = array();
2101
2102 // Migrate regular notifications
2103 self::migrate_notifications_to_action( $form_options, $form_id, $notifications );
2104
2105 // Migrate autoresponders
2106 self::migrate_autoresponder_to_action( $form_options, $form_id, $notifications );
2107
2108 if ( empty( $notifications ) ) {
2109 return;
2110 }
2111
2112 foreach ( $notifications as $new_notification ) {
2113 $new_notification['post_type'] = $post_type;
2114 $new_notification['post_excerpt'] = 'email';
2115 $new_notification['post_title'] = __( 'Email Notification', 'formidable' );
2116 $new_notification['menu_order'] = $form_id;
2117 $new_notification['post_status'] = 'publish';
2118
2119 // Switch field IDs and keys, if needed
2120 if ( $switch ) {
2121
2122 // Switch field IDs in email conditional logic
2123 self::switch_email_condition_field_ids( $new_notification['post_content'] );
2124
2125 // Switch all other field IDs in email
2126 $new_notification['post_content'] = FrmFieldsHelper::switch_field_ids( $new_notification['post_content'] );
2127 }
2128 $new_notification['post_content'] = FrmAppHelper::prepare_and_encode( $new_notification['post_content'] );
2129
2130 $exists = get_posts(
2131 array(
2132 'name' => $new_notification['post_name'],
2133 'post_type' => $new_notification['post_type'],
2134 'post_status' => $new_notification['post_status'],
2135 'numberposts' => 1,
2136 )
2137 );
2138
2139 if ( empty( $exists ) ) {
2140 FrmDb::save_json_post( $new_notification );
2141 ++$imported['imported']['actions'];
2142 }
2143 unset( $new_notification );
2144 }//end foreach
2145
2146 self::remove_deprecated_notification_settings( $form_id, $form_options );
2147 }
2148
2149 /**
2150 * Remove deprecated notification settings after migration
2151 *
2152 * @since 2.05
2153 *
2154 * @param int|string $form_id
2155 * @param array $form_options
2156 *
2157 * @return void
2158 */
2159 private static function remove_deprecated_notification_settings( $form_id, $form_options ) {
2160 $delete_settings = array( 'notification', 'autoresponder', 'email_to' );
2161
2162 foreach ( $delete_settings as $index ) {
2163 if ( isset( $form_options[ $index ] ) ) {
2164 unset( $form_options[ $index ] );
2165 }
2166 }
2167 FrmForm::update( $form_id, array( 'options' => $form_options ) );
2168 }
2169
2170 /**
2171 * Migrate notifications to action
2172 *
2173 * @param array $form_options
2174 * @param int $form_id
2175 * @param array $notifications
2176 *
2177 * @return void
2178 */
2179 private static function migrate_notifications_to_action( $form_options, $form_id, &$notifications ) {
2180 if ( ! isset( $form_options['notification'] ) && ! empty( $form_options['email_to'] ) ) {
2181 // add old settings into notification array
2182 $form_options['notification'] = array( 0 => $form_options );
2183 } elseif ( isset( $form_options['notification']['email_to'] ) ) {
2184 // make sure it's in the correct format
2185 $form_options['notification'] = array( 0 => $form_options['notification'] );
2186 }
2187
2188 if ( isset( $form_options['notification'] ) && is_array( $form_options['notification'] ) ) {
2189 foreach ( $form_options['notification'] as $email_key => $notification ) {
2190
2191 $atts = array(
2192 'email_to' => '',
2193 'reply_to' => '',
2194 'reply_to_name' => '',
2195 'event' => '',
2196 'form_id' => $form_id,
2197 'email_key' => $email_key,
2198 );
2199
2200 // Format the email data
2201 self::format_email_data( $atts, $notification );
2202
2203 if ( isset( $notification['twilio'] ) && $notification['twilio'] ) {
2204 do_action( 'frm_create_twilio_action', $atts, $notification );
2205 }
2206
2207 // Setup the new notification
2208 $new_notification = array();
2209 self::setup_new_notification( $new_notification, $notification, $atts );
2210
2211 $notifications[] = $new_notification;
2212 }//end foreach
2213 }//end if
2214 }
2215
2216 /**
2217 * Format email data
2218 *
2219 * @param array $atts
2220 * @param array $notification
2221 *
2222 * @return void
2223 */
2224 private static function format_email_data( &$atts, $notification ) {
2225 // Format email_to
2226 self::format_email_to_data( $atts, $notification );
2227
2228 // Format the reply to email and name
2229 $reply_fields = array(
2230 'reply_to' => '',
2231 'reply_to_name' => '',
2232 );
2233
2234 foreach ( $reply_fields as $f => $val ) {
2235 if ( isset( $notification[ $f ] ) ) {
2236 $atts[ $f ] = $notification[ $f ];
2237
2238 if ( 'custom' == $notification[ $f ] ) {
2239 $atts[ $f ] = $notification[ 'cust_' . $f ];
2240 } elseif ( is_numeric( $atts[ $f ] ) && ! empty( $atts[ $f ] ) ) {
2241 $atts[ $f ] = '[' . $atts[ $f ] . ']';
2242 }
2243 }
2244 unset( $f, $val );
2245 }
2246
2247 // Format event
2248 $atts['event'] = array( 'create' );
2249
2250 if ( isset( $notification['update_email'] ) && 1 == $notification['update_email'] ) {
2251 $atts['event'][] = 'update';
2252 } elseif ( isset( $notification['update_email'] ) && 2 == $notification['update_email'] ) {
2253 $atts['event'] = array( 'update' );
2254 }
2255 }
2256
2257 /**
2258 * Format email_to data
2259 *
2260 * @param array $atts
2261 * @param array $notification
2262 *
2263 * @return void
2264 */
2265 private static function format_email_to_data( &$atts, $notification ) {
2266 if ( isset( $notification['email_to'] ) ) {
2267 $atts['email_to'] = preg_split( '/ (,|;) /', $notification['email_to'] );
2268 } else {
2269 $atts['email_to'] = array();
2270 }
2271
2272 if ( isset( $notification['also_email_to'] ) ) {
2273 $email_fields = (array) $notification['also_email_to'];
2274 $atts['email_to'] = array_merge( $email_fields, $atts['email_to'] );
2275 unset( $email_fields );
2276 }
2277
2278 foreach ( $atts['email_to'] as $key => $email_field ) {
2279
2280 if ( is_numeric( $email_field ) ) {
2281 $atts['email_to'][ $key ] = '[' . $email_field . ']';
2282 }
2283
2284 if ( strpos( $email_field, '|' ) ) {
2285 $email_opt = explode( '|', $email_field );
2286
2287 if ( isset( $email_opt[0] ) ) {
2288 $atts['email_to'][ $key ] = '[' . $email_opt[0] . ' show=' . $email_opt[1] . ']';
2289 }
2290 unset( $email_opt );
2291 }
2292 }
2293 $atts['email_to'] = implode( ', ', $atts['email_to'] );
2294 }
2295
2296 /**
2297 * Setup new notification
2298 *
2299 * @param array $new_notification
2300 * @param array $notification
2301 * @param array $atts
2302 *
2303 * @return void
2304 */
2305 private static function setup_new_notification( &$new_notification, $notification, $atts ) {
2306 // Set up new notification
2307 $new_notification = array(
2308 'post_content' => array(
2309 'email_to' => $atts['email_to'],
2310 'event' => $atts['event'],
2311 ),
2312 'post_name' => $atts['form_id'] . '_email_' . $atts['email_key'],
2313 );
2314
2315 // Add more fields to the new notification
2316 $add_fields = array( 'email_message', 'email_subject', 'plain_text', 'inc_user_info', 'conditions' );
2317
2318 foreach ( $add_fields as $add_field ) {
2319 if ( isset( $notification[ $add_field ] ) ) {
2320 $new_notification['post_content'][ $add_field ] = $notification[ $add_field ];
2321 } elseif ( in_array( $add_field, array( 'plain_text', 'inc_user_info' ) ) ) {
2322 $new_notification['post_content'][ $add_field ] = 0;
2323 } else {
2324 $new_notification['post_content'][ $add_field ] = '';
2325 }
2326 unset( $add_field );
2327 }
2328
2329 // Set reply to
2330 $new_notification['post_content']['reply_to'] = $atts['reply_to'];
2331
2332 // Set from
2333 if ( ! empty( $atts['reply_to'] ) || ! empty( $atts['reply_to_name'] ) ) {
2334 $new_notification['post_content']['from'] = ( empty( $atts['reply_to_name'] ) ? '[sitename]' : $atts['reply_to_name'] ) . ' <' . ( empty( $atts['reply_to'] ) ? '[admin_email]' : $atts['reply_to'] ) . '>';
2335 }
2336 }
2337
2338 /**
2339 * Switch field IDs in pre-2.0 email conditional logic
2340 *
2341 * @param array $post_content Pass by reference.
2342 *
2343 * @return void
2344 */
2345 private static function switch_email_condition_field_ids( &$post_content ) {
2346 // Switch field IDs in conditional logic
2347 if ( isset( $post_content['conditions'] ) && is_array( $post_content['conditions'] ) ) {
2348 foreach ( $post_content['conditions'] as $email_key => $val ) {
2349 if ( is_numeric( $email_key ) ) {
2350 $post_content['conditions'][ $email_key ] = self::switch_action_field_ids( $val, array( 'hide_field' ) );
2351 }
2352 unset( $email_key, $val );
2353 }
2354 }
2355 }
2356
2357 /**
2358 * Migrate autoresponder to action
2359 *
2360 * @param array $form_options
2361 * @param int $form_id
2362 * @param array $notifications
2363 *
2364 * @return void
2365 */
2366 private static function migrate_autoresponder_to_action( $form_options, $form_id, &$notifications ) {
2367 if ( isset( $form_options['auto_responder'] ) && $form_options['auto_responder'] && isset( $form_options['ar_email_message'] ) && $form_options['ar_email_message'] ) {
2368 // migrate autoresponder
2369
2370 $email_field = $form_options['ar_email_to'] ?? 0;
2371
2372 if ( strpos( $email_field, '|' ) ) {
2373 // data from entries field
2374 $email_field = explode( '|', $email_field );
2375
2376 if ( isset( $email_field[1] ) ) {
2377 $email_field = $email_field[1];
2378 }
2379 }
2380
2381 if ( is_numeric( $email_field ) && ! empty( $email_field ) ) {
2382 $email_field = '[' . $email_field . ']';
2383 }
2384
2385 $notification = $form_options;
2386 $new_notification2 = array(
2387 'post_content' => array(
2388 'email_message' => $notification['ar_email_message'],
2389 'email_subject' => $notification['ar_email_subject'] ?? '',
2390 'email_to' => $email_field,
2391 'plain_text' => $notification['ar_plain_text'] ?? 0,
2392 'inc_user_info' => 0,
2393 ),
2394 'post_name' => $form_id . '_email_' . count( $notifications ),
2395 );
2396
2397 $reply_to = $notification['ar_reply_to'] ?? '';
2398 $reply_to_name = $notification['ar_reply_to_name'] ?? '';
2399
2400 if ( ! empty( $reply_to ) ) {
2401 $new_notification2['post_content']['reply_to'] = $reply_to;
2402 }
2403
2404 if ( ! empty( $reply_to ) || ! empty( $reply_to_name ) ) {
2405 $new_notification2['post_content']['from'] = ( empty( $reply_to_name ) ? '[sitename]' : $reply_to_name ) . ' <' . ( empty( $reply_to ) ? '[admin_email]' : $reply_to ) . '>';
2406 }
2407
2408 $notifications[] = $new_notification2;
2409 unset( $new_notification2 );
2410 }//end if
2411 }
2412
2413 /**
2414 * PHP 8 backward compatibility for the libxml_disable_entity_loader function
2415 *
2416 * @param bool $loader
2417 *
2418 * @return bool
2419 */
2420 public static function maybe_libxml_disable_entity_loader( $loader ) {
2421 if ( version_compare( phpversion(), '8.0', '<' ) && function_exists( 'libxml_disable_entity_loader' ) ) {
2422 $loader = libxml_disable_entity_loader( $loader ); // phpcs:disable Generic.PHP.DeprecatedFunctions.Deprecated
2423 }
2424
2425 return $loader;
2426 }
2427
2428 /**
2429 * PHP 8 backward compatibility for the libxml_disable_entity_loader function
2430 *
2431 * @return bool
2432 */
2433 public static function check_if_libxml_disable_entity_loader_exists() {
2434 return version_compare( phpversion(), '8.0', '<' ) && ! function_exists( 'libxml_disable_entity_loader' );
2435 }
2436
2437 /**
2438 * Get the file types allowed for importing.
2439 * This is used in the "accept" attribute for the file upload input on the XML import page.
2440 *
2441 * @since 6.8.3
2442 *
2443 * @return array
2444 */
2445 public static function get_supported_upload_file_types() {
2446 $file_types = array( '.xml' );
2447
2448 if ( FrmAppHelper::pro_is_installed() ) {
2449 // CSV Importing is only available in Pro.
2450 $file_types[] = '.csv';
2451 }
2452 return $file_types;
2453 }
2454 }
2455