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

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