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

2,183 lines 65.6 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 // Fix issue with line breaks appearing in descriptions as "rn".
930 if ( $post['post_type'] === $form_action_type ) {
931 $post['post_content'] = str_replace( '\\\\r\\\\n', '\\r\\n', $post['post_content'] );
932 }
933
934 $old_id = $post['post_id'];
935 self::populate_post( $post, $item, $imported );
936
937 unset( $item );
938
939 $post_id = false;
940 if ( $post['post_type'] === $form_action_type ) {
941 $action_control = FrmFormActionsController::get_form_actions( $post['post_excerpt'] );
942 if ( $action_control && is_object( $action_control ) && isset( $imported['form_status'] ) ) {
943 $post_id = $action_control->maybe_create_action( $post, $imported['form_status'] );
944 }
945 unset( $action_control );
946 } elseif ( $post['post_type'] === 'frm_styles' ) {
947 // Properly encode post content before inserting the post
948 $post['post_content'] = FrmAppHelper::maybe_json_decode( $post['post_content'] );
949 $post['post_content'] = FrmAppHelper::prepare_and_encode( $post['post_content'] );
950 // Store template data additionally to a postmeta that can be used to revert the style to default template style.
951 $post['postmeta']['frm_style_default'] = $post['post_content'];
952
953 // Create/update post now
954 $post_id = wp_insert_post( $post );
955 } else {
956 if ( $post['post_type'] === 'frm_display' ) {
957 $post['post_content'] = self::maybe_prepare_json_view_content( $post['post_content'] );
958 } elseif ( 'page' === $post['post_type'] && isset( $imported['posts'][ $post['post_parent'] ] ) ) {
959 $post['post_parent'] = $imported['posts'][ $post['post_parent'] ];
960 }
961 // Create/update post now
962 $post_id = wp_insert_post( $post );
963 }//end if
964
965 if ( ! is_numeric( $post_id ) ) {
966 continue;
967 }
968
969 if ( false !== strpos( $post['post_content'], '[display-frm-data' ) || false !== strpos( $post['post_content'], '[formidable' ) ) {
970 $posts_with_shortcodes[ $post_id ] = $post;
971 }
972
973 self::update_postmeta( $post, $post_id );
974 self::update_layout( $post, $post_id );
975
976 $this_type = 'posts';
977 if ( isset( $post_types[ $post['post_type'] ] ) ) {
978 $this_type = $post_types[ $post['post_type'] ];
979 }
980
981 if ( isset( $post['ID'] ) && $post_id == $post['ID'] ) {
982 ++$imported['updated'][ $this_type ];
983 } else {
984 ++$imported['imported'][ $this_type ];
985 }
986
987 $imported['posts'][ $old_id ] = $post_id;
988
989 if ( $post['post_type'] === 'frm_display' ) {
990 $view_ids[ $old_id ] = $post_id;
991 }
992
993 do_action( 'frm_after_import_view', $post_id, $post );
994
995 unset( $post );
996 }//end foreach
997
998 if ( $posts_with_shortcodes && $view_ids ) {
999 self::maybe_switch_view_ids_after_importing_posts( $posts_with_shortcodes, $view_ids );
1000 }
1001 unset( $posts_with_shortcodes, $view_ids );
1002
1003 if ( ! empty( $imported['forms'] ) ) {
1004 // clear imported forms style cache to make sure the new styles are applied to the forms
1005 self::clear_forms_style_caches( $imported['forms'] );
1006 }
1007
1008 self::maybe_update_stylesheet( $imported );
1009
1010 flush_rewrite_rules();
1011
1012 return $imported;
1013 }
1014
1015 /**
1016 * Clears styles from cache for imported forms
1017 *
1018 * @param array $imported_forms
1019 */
1020 private static function clear_forms_style_caches( $imported_forms ) {
1021 $where = array(
1022 'id' => $imported_forms,
1023 'options LIKE' => '"old_style"',
1024 );
1025 $forms = FrmDb::get_results( 'frm_forms', $where );
1026
1027 foreach ( $forms as $form ) {
1028 FrmAppHelper::unserialize_or_decode( $form->options );
1029 if ( ! $form->options ) {
1030 continue;
1031 }
1032 $where = array(
1033 'post_name' => $form->options['old_style'],
1034 'post_type' => FrmStylesController::$post_type,
1035 );
1036
1037 $select = 'ID';
1038
1039 $cache_key = FrmDb::generate_cache_key( $where, array( 'limit' => 1 ), $select, 'var' );
1040 FrmDb::delete_cache_and_transient( $cache_key, 'post' );
1041 }
1042 }
1043
1044 /**
1045 * Replace old form ids with new ones in a string.
1046 *
1047 * @param string $string
1048 * @param array<int> $form_ids new form ids indexed by old form id.
1049 * @return string
1050 */
1051 private static function switch_form_ids( $string, $form_ids ) {
1052 if ( false === strpos( $string, '[formidable' ) ) {
1053 // Skip string replacing if there are no form shortcodes in string.
1054 return $string;
1055 }
1056
1057 foreach ( $form_ids as $old_id => $new_id ) {
1058 $string = str_replace(
1059 array(
1060 '[formidable id="' . $old_id . '"',
1061 '[formidable id=' . $old_id . ']',
1062 '[formidable id=' . $old_id . ' ',
1063 '"formId":"' . $old_id . '"',
1064 ),
1065 array(
1066 '[formidable id="' . $new_id . '"',
1067 '[formidable id=' . $new_id . ']',
1068 '[formidable id=' . $new_id . ' ',
1069 '"formId":"' . $new_id . '"',
1070 ),
1071 $string
1072 );
1073 }
1074
1075 return $string;
1076 }
1077
1078 /**
1079 * @param array<array> $posts_with_shortcodes indexed by current post id.
1080 * @param array<int> $view_ids new view ids indexed by old view id.
1081 * @return void
1082 */
1083 private static function maybe_switch_view_ids_after_importing_posts( $posts_with_shortcodes, $view_ids ) {
1084 foreach ( $posts_with_shortcodes as $imported_post_id => $post ) {
1085 $post_content = self::switch_view_ids( $post['post_content'], $view_ids );
1086 if ( $post_content === $post['post_content'] ) {
1087 continue;
1088 }
1089
1090 wp_update_post(
1091 array(
1092 'ID' => $imported_post_id,
1093 'post_content' => $post_content,
1094 )
1095 );
1096 }
1097 }
1098
1099 /**
1100 * Replace old view ids with new ones in a string.
1101 *
1102 * @param string $string
1103 * @param array<int> $view_ids new view ids indexed by old view id.
1104 * @return string
1105 */
1106 private static function switch_view_ids( $string, $view_ids ) {
1107 if ( false === strpos( $string, '[display-frm-data' ) ) {
1108 // Skip string replacing if there are no view shortcodes in string.
1109 return $string;
1110 }
1111
1112 foreach ( $view_ids as $old_id => $new_id ) {
1113 $string = str_replace(
1114 array(
1115 '[display-frm-data id="' . $old_id . '"',
1116 '[display-frm-data id=' . $old_id . ']',
1117 '[display-frm-data id=' . $old_id . ' ',
1118 '"viewId":"' . $old_id . '"',
1119 ),
1120 array(
1121 '[display-frm-data id="' . $new_id . '"',
1122 '[display-frm-data id=' . $new_id . ']',
1123 '[display-frm-data id=' . $new_id . ' ',
1124 '"viewId":"' . $new_id . '"',
1125 ),
1126 $string
1127 );
1128 unset( $old_id, $new_id );
1129 }
1130
1131 return $string;
1132 }
1133
1134 /**
1135 * @param string $content
1136 * @return string
1137 */
1138 private static function maybe_prepare_json_view_content( $content ) {
1139 $maybe_decoded = FrmAppHelper::maybe_json_decode( $content );
1140 if ( is_array( $maybe_decoded ) && isset( $maybe_decoded[0] ) && isset( $maybe_decoded[0]['box'] ) ) {
1141 return FrmAppHelper::prepare_and_encode( $maybe_decoded );
1142 }
1143 return $content;
1144 }
1145
1146 private static function populate_post( &$post, $item, $imported ) {
1147 if ( isset( $item->attachment_url ) ) {
1148 $post['attachment_url'] = (string) $item->attachment_url;
1149 }
1150
1151 if ( $post['post_type'] == FrmFormActionsController::$action_post_type && isset( $imported['forms'][ (int) $post['menu_order'] ] ) ) {
1152 // update to new form id
1153 $post['menu_order'] = $imported['forms'][ (int) $post['menu_order'] ];
1154 }
1155
1156 // Don't allow default styles to take over a site's default style
1157 if ( 'frm_styles' == $post['post_type'] ) {
1158 $post['menu_order'] = 0;
1159 }
1160
1161 foreach ( $item->postmeta as $meta ) {
1162 self::populate_postmeta( $post, $meta, $imported );
1163 unset( $meta );
1164 }
1165
1166 foreach ( $item->layout as $layout ) {
1167 self::populate_layout( $post, $layout );
1168 unset( $layout );
1169 }
1170
1171 self::populate_taxonomies( $post, $item );
1172
1173 self::maybe_editing_post( $post );
1174 }
1175
1176 /**
1177 * @param array $post
1178 * @param stdClass $meta
1179 * @param array $imported
1180 */
1181 private static function populate_postmeta( &$post, $meta, $imported ) {
1182 global $frm_duplicate_ids;
1183
1184 $m = array(
1185 'key' => (string) $meta->meta_key,
1186 'value' => (string) $meta->meta_value,
1187 );
1188
1189 // Switch old form and field ids to new ones.
1190 if ( 'frm_form_id' === $m['key'] && isset( $imported['forms'][ (int) $m['value'] ] ) ) {
1191 $m['value'] = $imported['forms'][ (int) $m['value'] ];
1192 } else {
1193 $m['value'] = FrmAppHelper::maybe_json_decode( $m['value'] );
1194
1195 if ( ! empty( $frm_duplicate_ids ) ) {
1196 if ( 'frm_dyncontent' === $m['key'] ) {
1197 $m['value'] = self::maybe_prepare_json_view_content( $m['value'] );
1198 $m['value'] = FrmFieldsHelper::switch_field_ids( $m['value'] );
1199 } elseif ( 'frm_options' === $m['key'] ) {
1200
1201 foreach ( array( 'date_field_id', 'edate_field_id' ) as $setting_name ) {
1202 if ( isset( $m['value'][ $setting_name ] ) && is_numeric( $m['value'][ $setting_name ] ) && isset( $frm_duplicate_ids[ $m['value'][ $setting_name ] ] ) ) {
1203 $m['value'][ $setting_name ] = $frm_duplicate_ids[ $m['value'][ $setting_name ] ];
1204 }
1205 }
1206
1207 if ( ! empty( $m['value']['map_address_fields'] ) ) {
1208 foreach ( $m['value']['map_address_fields'] as $address_field_key => $address_field_id ) {
1209 if ( isset( $frm_duplicate_ids[ $address_field_id ] ) ) {
1210 $m['value']['map_address_fields'][ $address_field_key ] = $frm_duplicate_ids[ $address_field_id ];
1211 }
1212 }
1213 }
1214
1215 if ( ! empty( $m['value']['calendar_options'] ) ) {
1216 foreach ( $m['value']['calendar_options'] as $calendar_option_group_key => $calendar_option ) {
1217 if ( isset( $frm_duplicate_ids[ $calendar_option['value'] ] ) ) {
1218 $m['value']['calendar_options'][ $calendar_option_group_key ]['value'] = $frm_duplicate_ids[ $calendar_option['value'] ];
1219 }
1220 }
1221 }
1222
1223 if ( ! empty( $m['value']['timeline_options'] ) ) {
1224 foreach ( $m['value']['timeline_options'] as $timeline_option_group_key => $timeline_group_option ) {
1225 foreach ( $timeline_group_option as $timeline_option_key => $timeline_option ) {
1226 if ( isset( $frm_duplicate_ids[ $timeline_option ] ) ) {
1227 $m['value']['timeline_options'][ $timeline_option_group_key ][ $timeline_option_key ] = $frm_duplicate_ids[ $timeline_option ];
1228 }
1229 }
1230 }
1231 }
1232
1233 $check_dup_array = array();
1234 if ( ! empty( $m['value']['order_by'] ) ) {
1235 if ( is_numeric( $m['value']['order_by'] ) && isset( $frm_duplicate_ids[ $m['value']['order_by'] ] ) ) {
1236 $m['value']['order_by'] = $frm_duplicate_ids[ $m['value']['order_by'] ];
1237 } elseif ( is_array( $m['value']['order_by'] ) ) {
1238 $check_dup_array[] = 'order_by';
1239 }
1240 }
1241
1242 if ( ! empty( $m['value']['where'] ) ) {
1243 $check_dup_array[] = 'where';
1244 }
1245
1246 foreach ( $check_dup_array as $check_k ) {
1247 foreach ( (array) $m['value'][ $check_k ] as $mk => $mv ) {
1248 if ( isset( $frm_duplicate_ids[ $mv ] ) ) {
1249 $m['value'][ $check_k ][ $mk ] = $frm_duplicate_ids[ $mv ];
1250 }
1251 unset( $mk, $mv );
1252 }
1253 }
1254 }//end if
1255 }//end if
1256 }//end if
1257
1258 if ( ! is_array( $m['value'] ) ) {
1259 $m['value'] = FrmAppHelper::maybe_json_decode( $m['value'] );
1260 }
1261
1262 $post['postmeta'][ (string) $meta->meta_key ] = $m['value'];
1263 }
1264
1265 private static function populate_layout( &$post, $layout ) {
1266 $post['layout'][ (string) $layout->type ] = (string) $layout->data;
1267 }
1268
1269 /**
1270 * Add terms to post
1271 *
1272 * @param array $post By reference.
1273 * @param object $item The XML object data.
1274 */
1275 private static function populate_taxonomies( &$post, $item ) {
1276 foreach ( $item->category as $c ) {
1277 $att = $c->attributes();
1278 if ( ! isset( $att['nicename'] ) ) {
1279 continue;
1280 }
1281
1282 $taxonomy = (string) $att['domain'];
1283 if ( is_taxonomy_hierarchical( $taxonomy ) ) {
1284 $name = (string) $att['nicename'];
1285 $h_term = get_term_by( 'slug', $name, $taxonomy );
1286 if ( $h_term ) {
1287 $name = $h_term->term_id;
1288 }
1289 unset( $h_term );
1290 } else {
1291 $name = (string) $c;
1292 }
1293
1294 if ( ! isset( $post['tax_input'][ $taxonomy ] ) ) {
1295 $post['tax_input'][ $taxonomy ] = array();
1296 }
1297
1298 $post['tax_input'][ $taxonomy ][] = $name;
1299 unset( $name );
1300 }//end foreach
1301 }
1302
1303 /**
1304 * Edit post if the key and created time match.
1305 */
1306 private static function maybe_editing_post( &$post ) {
1307 $match_by = array(
1308 'post_type' => $post['post_type'],
1309 'name' => $post['post_name'],
1310 'post_status' => $post['post_status'],
1311 'posts_per_page' => 1,
1312 );
1313
1314 if ( in_array( $post['post_status'], array( 'trash', 'draft' ), true ) ) {
1315 $match_by['include'] = $post['post_id'];
1316 unset( $match_by['name'] );
1317 }
1318
1319 $editing = get_posts( $match_by );
1320
1321 if ( ! empty( $editing ) && current( $editing )->post_date == $post['post_date'] ) {
1322 // set the id of the post to edit
1323 $post['ID'] = current( $editing )->ID;
1324 }
1325 }
1326
1327 /**
1328 * @param array $post
1329 * @param int $post_id
1330 * @return void
1331 */
1332 private static function update_postmeta( &$post, $post_id ) {
1333 foreach ( $post['postmeta'] as $k => $v ) {
1334 switch ( $k ) {
1335 case '_edit_last':
1336 $v = FrmAppHelper::get_user_id_param( $v );
1337 break;
1338
1339 case '_thumbnail_id':
1340 if ( FrmAppHelper::pro_is_installed() ) {
1341 // Change the attachment ID.
1342 $field_obj = FrmFieldFactory::get_field_type( 'file' );
1343 $v = $field_obj->get_file_id( $v );
1344 }
1345 break;
1346
1347 case 'frm_dyncontent':
1348 if ( is_array( $v ) ) {
1349 $v = json_encode( $v );
1350 }
1351 break;
1352
1353 case 'frm_param':
1354 add_rewrite_endpoint( $v, EP_PERMALINK | EP_PAGES );
1355 break;
1356 }//end switch
1357
1358 update_post_meta( $post_id, $k, $v );
1359 unset( $k, $v );
1360 }//end foreach
1361 }
1362
1363 /**
1364 * @param array $post
1365 * @param int $post_id
1366 */
1367 private static function update_layout( &$post, $post_id ) {
1368 if ( is_callable( 'FrmViewsLayout::maybe_create_layouts_for_view' ) ) {
1369 $listing_layout = ! empty( $post['layout']['listing'] ) ? json_decode( $post['layout']['listing'], true ) : array();
1370 $detail_layout = ! empty( $post['layout']['detail'] ) ? json_decode( $post['layout']['detail'], true ) : array();
1371 if ( $listing_layout || $detail_layout ) {
1372 FrmViewsLayout::maybe_create_layouts_for_view( $post_id, $listing_layout, $detail_layout );
1373 }
1374 }
1375 }
1376
1377 private static function maybe_update_stylesheet( $imported ) {
1378 $new_styles = ! empty( $imported['imported']['styles'] );
1379 $updated_styles = ! empty( $imported['updated']['styles'] );
1380 if ( $new_styles || $updated_styles ) {
1381 if ( is_admin() && function_exists( 'get_filesystem_method' ) ) {
1382 $frm_style = new FrmStyle();
1383 $frm_style->update( 'default' );
1384 }
1385 foreach ( $imported['forms'] as $form_id ) {
1386 self::update_custom_style_setting_after_import( $form_id );
1387 }
1388 }
1389 }
1390
1391 /**
1392 * @param mixed $result
1393 * @param string $message
1394 * @param array $errors
1395 */
1396 public static function parse_message( $result, &$message, &$errors ) {
1397 if ( is_wp_error( $result ) ) {
1398 $errors[] = $result->get_error_message();
1399
1400 // Remove the SimpleXML_parse_error from the WP_Error object to avoid
1401 // displaying duplicate error messages from $result->get_error_message()
1402 $error_codes = $result->get_error_codes();
1403 $error_details = array();
1404 foreach ( $error_codes as $error_code ) {
1405 // Clone WP_Error data because WP_Error removes all error messages and data
1406 // associated with the specified error code when an item is removed.
1407 // Source: https://developer.wordpress.org/reference/classes/wp_error/remove/#source
1408 $error_details = $result->get_error_data( $error_code );
1409 if ( $error_code === 'SimpleXML_parse_error' ) {
1410 $result->remove( $error_code );
1411 break;
1412 }
1413 }
1414
1415 if ( ! empty( $error_details ) ) {
1416 $errors[] = '<br />' . esc_html_x( 'Error details:', 'import xml message', 'formidable' ) . '<br />' . esc_html( print_r( $error_details, 1 ) );
1417 }
1418
1419 return;
1420 }//end if
1421
1422 if ( ! $result ) {
1423 return;
1424 }
1425
1426 if ( ! is_array( $result ) ) {
1427 $message = is_string( $result ) ? $result : htmlentities( print_r( $result, 1 ) );
1428 return;
1429 }
1430
1431 $t_strings = array(
1432 'imported' => __( 'Imported', 'formidable' ),
1433 'updated' => __( 'Updated', 'formidable' ),
1434 );
1435
1436 $message = '<ul>';
1437 foreach ( $result as $type => $results ) {
1438 if ( ! isset( $t_strings[ $type ] ) ) {
1439 // only print imported and updated
1440 continue;
1441 }
1442
1443 $s_message = array();
1444 foreach ( $results as $k => $m ) {
1445 self::item_count_message( $m, $k, $s_message );
1446 unset( $k, $m );
1447 }
1448
1449 if ( ! empty( $s_message ) ) {
1450 $message .= '<li><strong>' . $t_strings[ $type ] . ':</strong> ';
1451 $message .= implode( ', ', $s_message );
1452 $message .= '</li>';
1453 }
1454 }
1455
1456 if ( $message === '<ul>' ) {
1457 $message = '';
1458 $errors[] = __( 'Nothing was imported or updated', 'formidable' );
1459 } else {
1460 self::add_form_link_to_message( $result, $message );
1461
1462 /**
1463 * @since 5.3
1464 *
1465 * @param string $message
1466 * @param array $result
1467 */
1468 $message = apply_filters( 'frm_xml_parsed_message', $message, $result );
1469 $message .= '</ul>';
1470 }
1471 }
1472
1473 /**
1474 * @param int $m
1475 * @param string $type
1476 * @param array<string> $s_message
1477 */
1478 public static function item_count_message( $m, $type, &$s_message ) {
1479 if ( ! $m ) {
1480 return;
1481 }
1482
1483 $strings = array(
1484 /* translators: %1$s: Number of items */
1485 'forms' => sprintf( _n( '%1$s Form', '%1$s Forms', $m, 'formidable' ), $m ),
1486 /* translators: %1$s: Number of items */
1487 'fields' => sprintf( _n( '%1$s Field', '%1$s Fields', $m, 'formidable' ), $m ),
1488 /* translators: %1$s: Number of items */
1489 'items' => sprintf( _n( '%1$s Entry', '%1$s Entries', $m, 'formidable' ), $m ),
1490 /* translators: %1$s: Number of items */
1491 'views' => sprintf( _n( '%1$s View', '%1$s Views', $m, 'formidable' ), $m ),
1492 /* translators: %1$s: Number of items */
1493 'posts' => sprintf( _n( '%1$s Page/Post', '%1$s Pages/Posts', $m, 'formidable' ), $m ),
1494 /* translators: %1$s: Number of items */
1495 'styles' => sprintf( _n( '%1$s Style', '%1$s Styles', $m, 'formidable' ), $m ),
1496 /* translators: %1$s: Number of items */
1497 'terms' => sprintf( _n( '%1$s Term', '%1$s Terms', $m, 'formidable' ), $m ),
1498 /* translators: %1$s: Number of items */
1499 'actions' => sprintf( _n( '%1$s Form Action', '%1$s Form Actions', $m, 'formidable' ), $m ),
1500 );
1501
1502 if ( isset( $strings[ $type ] ) ) {
1503 $s_message[] = $strings[ $type ];
1504 } else {
1505 $string = ' ' . $m . ' ' . ucfirst( $type );
1506
1507 /**
1508 * @since 5.3
1509 *
1510 * @param string $string Message string for imported item.
1511 * @param int $m Number of item that was imported.
1512 * }
1513 */
1514 $string = apply_filters( 'frm_xml_' . $type . '_count_message', $string, $m );
1515 $s_message[] = $string;
1516 }
1517 }
1518
1519 /**
1520 * If a single form was imported, include a link in the success message.
1521 *
1522 * @since 4.0
1523 * @param array $result The response from the XML import.
1524 * @param string $message The response shown on the page after import.
1525 */
1526 private static function add_form_link_to_message( $result, &$message ) {
1527 $total_forms = $result['imported']['forms'] + $result['updated']['forms'];
1528 if ( $total_forms > 1 ) {
1529 return;
1530 }
1531
1532 $primary_form = reset( $result['forms'] );
1533 if ( ! empty( $primary_form ) ) {
1534 $primary_form = FrmForm::getOne( $primary_form );
1535 $form_id = empty( $primary_form->parent_form_id ) ? $primary_form->id : $primary_form->parent_form_id;
1536
1537 $message .= '<li><a href="' . esc_url( FrmForm::get_edit_link( $form_id ) ) . '">' . esc_html__( 'Go to imported form', 'formidable' ) . '</a></li>';
1538 }
1539 }
1540
1541 /**
1542 * Prepare the form options for export
1543 *
1544 * @since 2.0.19
1545 *
1546 * @param string $options
1547 *
1548 * @return string
1549 */
1550 public static function prepare_form_options_for_export( $options ) {
1551 FrmAppHelper::unserialize_or_decode( $options );
1552 // Change custom_style to the post_name instead of ID (1 may be a string)
1553 $not_default = isset( $options['custom_style'] ) && 1 != $options['custom_style'];
1554 if ( $not_default ) {
1555 global $wpdb;
1556 $table = $wpdb->prefix . 'posts';
1557 $where = array( 'ID' => $options['custom_style'] );
1558 $select = 'post_name';
1559
1560 $style_name = FrmDb::get_var( $table, $where, $select );
1561
1562 if ( $style_name ) {
1563 $options['custom_style'] = $style_name;
1564 } else {
1565 $options['custom_style'] = 1;
1566 }
1567 }
1568 self::remove_default_form_options( $options );
1569 $options = serialize( $options );
1570
1571 return self::cdata( $options );
1572 }
1573
1574 /**
1575 * If the saved value is the same as the default, remove it from the export
1576 * This keeps file size down and prevents overriding global settings after import
1577 *
1578 * @since 3.06
1579 */
1580 private static function remove_default_form_options( &$options ) {
1581 $defaults = FrmFormsHelper::get_default_opts();
1582 if ( is_callable( 'FrmProFormsHelper::get_default_opts' ) ) {
1583 $defaults += FrmProFormsHelper::get_default_opts();
1584 }
1585 self::remove_defaults( $defaults, $options );
1586 }
1587
1588 /**
1589 * Remove extra settings from field to keep file size down
1590 *
1591 * @since 3.06
1592 */
1593 public static function prepare_field_for_export( &$field ) {
1594 self::remove_default_field_options( $field );
1595 self::add_image_src_to_image_options( $field );
1596 }
1597
1598 /**
1599 * Remove defaults from field options too
1600 *
1601 * @since 3.06
1602 */
1603 private static function remove_default_field_options( &$field ) {
1604 $defaults = self::default_field_options( $field->type );
1605 if ( empty( $defaults['blank'] ) ) {
1606 $global_settings = new FrmSettings();
1607 $global_defaults = $global_settings->default_options();
1608 $defaults['blank'] = $global_defaults['blank_msg'];
1609 }
1610
1611 $options = $field->field_options;
1612 FrmAppHelper::unserialize_or_decode( $options );
1613 self::remove_defaults( $defaults, $options );
1614 self::remove_default_html( 'custom_html', $defaults, $options );
1615
1616 // Get variations on the defaults.
1617 if ( isset( $options['invalid'] ) ) {
1618 $defaults = array(
1619 /* translators: %s: Field name */
1620 'invalid' => sprintf( __( '%s is invalid', 'formidable' ), $field->name ),
1621 );
1622 self::remove_defaults( $defaults, $options );
1623 }
1624
1625 $field->field_options = serialize( $options );
1626 }
1627
1628 /**
1629 * Add image "src" key to each image option so the image can be imported to another website.
1630 *
1631 * @since 5.5.1
1632 *
1633 * @param stdClass $field
1634 * @return void
1635 */
1636 private static function add_image_src_to_image_options( $field ) {
1637 if ( empty( $field->options ) || false === strpos( $field->options, 'image' ) ) {
1638 return;
1639 }
1640
1641 $updated = false;
1642 $options = $field->options;
1643 FrmAppHelper::unserialize_or_decode( $options );
1644
1645 if ( ! $options || ! is_array( $options ) ) {
1646 return;
1647 }
1648
1649 foreach ( $options as $key => $option ) {
1650 if ( is_array( $option ) && ! empty( $option['image'] ) ) {
1651 $options[ $key ]['src'] = wp_get_attachment_url( $option['image'] );
1652 $updated = true;
1653 }
1654 }
1655
1656 if ( $updated ) {
1657 $field->options = maybe_serialize( $options );
1658 }
1659 }
1660
1661 /**
1662 * @since 3.06.03
1663 */
1664 private static function default_field_options( $type ) {
1665 $defaults = FrmFieldsHelper::get_default_field_options( $type );
1666 if ( empty( $defaults['custom_html'] ) ) {
1667 $defaults['custom_html'] = FrmFieldsHelper::get_default_html( $type );
1668 }
1669 return $defaults;
1670 }
1671
1672 /**
1673 * Compare the default array to the saved values and
1674 * remove if they are the same
1675 *
1676 * @since 3.06
1677 */
1678 private static function remove_defaults( $defaults, &$saved ) {
1679 foreach ( $saved as $key => $value ) {
1680 if ( isset( $defaults[ $key ] ) && $defaults[ $key ] === $value ) {
1681 unset( $saved[ $key ] );
1682 }
1683 }
1684 }
1685
1686 /**
1687 * The line endings may prevent html from being equal when it should
1688 *
1689 * @since 3.06
1690 */
1691 private static function remove_default_html( $html_name, $defaults, &$options ) {
1692 if ( ! isset( $options[ $html_name ] ) || ! isset( $defaults[ $html_name ] ) ) {
1693 return;
1694 }
1695
1696 $old_html = str_replace( "\r\n", "\n", $options[ $html_name ] );
1697 $default_html = $defaults[ $html_name ];
1698 if ( $old_html == $default_html ) {
1699 unset( $options[ $html_name ] );
1700
1701 return;
1702 }
1703
1704 // Account for some of the older field default HTML.
1705 $default_html = str_replace( ' id="frm_desc_field_[key]"', '', $default_html );
1706 if ( $old_html == $default_html ) {
1707 unset( $options[ $html_name ] );
1708 }
1709 }
1710
1711 public static function cdata( $str ) {
1712 FrmAppHelper::unserialize_or_decode( $str );
1713 if ( is_array( $str ) ) {
1714 $str = json_encode( $str );
1715 } elseif ( FrmAppHelper::is_valid_utf8( $str ) === false ) {
1716 $str = FrmAppHelper::maybe_utf8_encode( $str );
1717 }
1718
1719 if ( is_numeric( $str ) ) {
1720 return $str;
1721 }
1722
1723 self::remove_invalid_characters_from_xml( $str );
1724
1725 // $str = ent2ncr(esc_html( $str));
1726 $str = '<![CDATA[' . str_replace( ']]>', ']]]]><![CDATA[>', $str ) . ']]>';
1727
1728 return $str;
1729 }
1730
1731 /**
1732 * Remove <US> character (unit separator) from exported strings
1733 *
1734 * @since 2.0.22
1735 *
1736 * @param string $str
1737 */
1738 private static function remove_invalid_characters_from_xml( &$str ) {
1739 // Remove <US> character
1740 $str = str_replace( '\x1F', '', $str );
1741 }
1742
1743 public static function migrate_form_settings_to_actions( $form_options, $form_id, &$imported = array(), $switch = false ) {
1744 // Get post type
1745 $post_type = FrmFormActionsController::$action_post_type;
1746
1747 // Set up imported index, if not set up yet
1748 if ( ! isset( $imported['imported']['actions'] ) ) {
1749 $imported['imported']['actions'] = 0;
1750 }
1751
1752 // Migrate post settings to action
1753 self::migrate_post_settings_to_action( $form_options, $form_id, $post_type, $imported, $switch );
1754
1755 // Migrate email settings to action
1756 self::migrate_email_settings_to_action( $form_options, $form_id, $post_type, $imported, $switch );
1757 }
1758
1759 /**
1760 * Migrate post settings to form action
1761 *
1762 * @param array $form_options
1763 * @param int $form_id
1764 * @param string $post_type
1765 * @param array $imported
1766 * @param bool $switch
1767 */
1768 private static function migrate_post_settings_to_action( $form_options, $form_id, $post_type, &$imported, $switch ) {
1769 if ( ! isset( $form_options['create_post'] ) || ! $form_options['create_post'] ) {
1770 return;
1771 }
1772
1773 $new_action = array(
1774 'post_type' => $post_type,
1775 'post_excerpt' => 'wppost',
1776 'post_title' => __( 'Create Posts', 'formidable' ),
1777 'menu_order' => $form_id,
1778 'post_status' => 'publish',
1779 'post_content' => array(),
1780 'post_name' => $form_id . '_wppost_1',
1781 );
1782
1783 $post_settings = array(
1784 'post_type',
1785 'post_category',
1786 'post_content',
1787 'post_excerpt',
1788 'post_title',
1789 'post_name',
1790 'post_date',
1791 'post_status',
1792 'post_custom_fields',
1793 'post_password',
1794 'post_parent',
1795 );
1796
1797 foreach ( $post_settings as $post_setting ) {
1798 if ( isset( $form_options[ $post_setting ] ) ) {
1799 $new_action['post_content'][ $post_setting ] = $form_options[ $post_setting ];
1800 }
1801 unset( $post_setting );
1802 }
1803
1804 $new_action['event'] = array( 'create', 'update' );
1805
1806 if ( $switch ) {
1807 // Fields with string or int saved.
1808 $basic_fields = array(
1809 'post_title',
1810 'post_content',
1811 'post_excerpt',
1812 'post_password',
1813 'post_date',
1814 'post_status',
1815 'post_parent',
1816 );
1817
1818 // Fields with arrays saved.
1819 $array_fields = array( 'post_category', 'post_custom_fields' );
1820
1821 $new_action['post_content'] = self::switch_action_field_ids( $new_action['post_content'], $basic_fields, $array_fields );
1822 }
1823 $new_action['post_content'] = json_encode( $new_action['post_content'] );
1824
1825 $exists = get_posts(
1826 array(
1827 'name' => $new_action['post_name'],
1828 'post_type' => $new_action['post_type'],
1829 'post_status' => $new_action['post_status'],
1830 'numberposts' => 1,
1831 )
1832 );
1833
1834 if ( ! $exists ) {
1835 // this isn't an email, but we need to use a class that will always be included
1836 FrmDb::save_json_post( $new_action );
1837 ++$imported['imported']['actions'];
1838 }
1839 }
1840
1841 /**
1842 * Switch old field IDs for new field IDs in emails and post
1843 *
1844 * @since 2.0
1845 *
1846 * @param array $post_content Check for old field IDs.
1847 * @param array $basic_fields Fields with string or int saved.
1848 * @param array $array_fields Fields with arrays saved.
1849 *
1850 * @return string $post_content - new field IDs
1851 */
1852 private static function switch_action_field_ids( $post_content, $basic_fields, $array_fields = array() ) {
1853 global $frm_duplicate_ids;
1854
1855 // If there aren't IDs that were switched, end now
1856 if ( ! $frm_duplicate_ids ) {
1857 return;
1858 }
1859
1860 // Get old IDs
1861 $old = array_keys( $frm_duplicate_ids );
1862
1863 // Get new IDs
1864 $new = array_values( $frm_duplicate_ids );
1865
1866 // Do a str_replace with each item to set the new IDs
1867 foreach ( $post_content as $key => $setting ) {
1868 if ( ! is_array( $setting ) && in_array( $key, $basic_fields ) ) {
1869 // Replace old IDs with new IDs
1870 $post_content[ $key ] = str_replace( $old, $new, $setting );
1871 } elseif ( is_array( $setting ) && in_array( $key, $array_fields ) ) {
1872 foreach ( $setting as $k => $val ) {
1873 // Replace old IDs with new IDs
1874 $post_content[ $key ][ $k ] = str_replace( $old, $new, $val );
1875 }
1876 }
1877 unset( $key, $setting );
1878 }
1879
1880 return $post_content;
1881 }
1882
1883 private static function migrate_email_settings_to_action( $form_options, $form_id, $post_type, &$imported, $switch ) {
1884 // No old notifications or autoresponders to carry over
1885 if ( ! isset( $form_options['auto_responder'] ) && ! isset( $form_options['notification'] ) && ! isset( $form_options['email_to'] ) ) {
1886 return;
1887 }
1888
1889 // Initialize notifications array
1890 $notifications = array();
1891
1892 // Migrate regular notifications
1893 self::migrate_notifications_to_action( $form_options, $form_id, $notifications );
1894
1895 // Migrate autoresponders
1896 self::migrate_autoresponder_to_action( $form_options, $form_id, $notifications );
1897
1898 if ( empty( $notifications ) ) {
1899 return;
1900 }
1901
1902 foreach ( $notifications as $new_notification ) {
1903 $new_notification['post_type'] = $post_type;
1904 $new_notification['post_excerpt'] = 'email';
1905 $new_notification['post_title'] = __( 'Email Notification', 'formidable' );
1906 $new_notification['menu_order'] = $form_id;
1907 $new_notification['post_status'] = 'publish';
1908
1909 // Switch field IDs and keys, if needed
1910 if ( $switch ) {
1911
1912 // Switch field IDs in email conditional logic
1913 self::switch_email_condition_field_ids( $new_notification['post_content'] );
1914
1915 // Switch all other field IDs in email
1916 $new_notification['post_content'] = FrmFieldsHelper::switch_field_ids( $new_notification['post_content'] );
1917 }
1918 $new_notification['post_content'] = FrmAppHelper::prepare_and_encode( $new_notification['post_content'] );
1919
1920 $exists = get_posts(
1921 array(
1922 'name' => $new_notification['post_name'],
1923 'post_type' => $new_notification['post_type'],
1924 'post_status' => $new_notification['post_status'],
1925 'numberposts' => 1,
1926 )
1927 );
1928
1929 if ( empty( $exists ) ) {
1930 FrmDb::save_json_post( $new_notification );
1931 ++$imported['imported']['actions'];
1932 }
1933 unset( $new_notification );
1934 }//end foreach
1935
1936 self::remove_deprecated_notification_settings( $form_id, $form_options );
1937 }
1938
1939 /**
1940 * Remove deprecated notification settings after migration
1941 *
1942 * @since 2.05
1943 *
1944 * @param int|string $form_id
1945 * @param array $form_options
1946 */
1947 private static function remove_deprecated_notification_settings( $form_id, $form_options ) {
1948 $delete_settings = array( 'notification', 'autoresponder', 'email_to' );
1949 foreach ( $delete_settings as $index ) {
1950 if ( isset( $form_options[ $index ] ) ) {
1951 unset( $form_options[ $index ] );
1952 }
1953 }
1954 FrmForm::update( $form_id, array( 'options' => $form_options ) );
1955 }
1956
1957 private static function migrate_notifications_to_action( $form_options, $form_id, &$notifications ) {
1958 if ( ! isset( $form_options['notification'] ) && ! empty( $form_options['email_to'] ) ) {
1959 // add old settings into notification array
1960 $form_options['notification'] = array( 0 => $form_options );
1961 } elseif ( isset( $form_options['notification']['email_to'] ) ) {
1962 // make sure it's in the correct format
1963 $form_options['notification'] = array( 0 => $form_options['notification'] );
1964 }
1965
1966 if ( isset( $form_options['notification'] ) && is_array( $form_options['notification'] ) ) {
1967 foreach ( $form_options['notification'] as $email_key => $notification ) {
1968
1969 $atts = array(
1970 'email_to' => '',
1971 'reply_to' => '',
1972 'reply_to_name' => '',
1973 'event' => '',
1974 'form_id' => $form_id,
1975 'email_key' => $email_key,
1976 );
1977
1978 // Format the email data
1979 self::format_email_data( $atts, $notification );
1980
1981 if ( isset( $notification['twilio'] ) && $notification['twilio'] ) {
1982 do_action( 'frm_create_twilio_action', $atts, $notification );
1983 }
1984
1985 // Setup the new notification
1986 $new_notification = array();
1987 self::setup_new_notification( $new_notification, $notification, $atts );
1988
1989 $notifications[] = $new_notification;
1990 }//end foreach
1991 }//end if
1992 }
1993
1994 private static function format_email_data( &$atts, $notification ) {
1995 // Format email_to
1996 self::format_email_to_data( $atts, $notification );
1997
1998 // Format the reply to email and name
1999 $reply_fields = array(
2000 'reply_to' => '',
2001 'reply_to_name' => '',
2002 );
2003 foreach ( $reply_fields as $f => $val ) {
2004 if ( isset( $notification[ $f ] ) ) {
2005 $atts[ $f ] = $notification[ $f ];
2006 if ( 'custom' == $notification[ $f ] ) {
2007 $atts[ $f ] = $notification[ 'cust_' . $f ];
2008 } elseif ( is_numeric( $atts[ $f ] ) && ! empty( $atts[ $f ] ) ) {
2009 $atts[ $f ] = '[' . $atts[ $f ] . ']';
2010 }
2011 }
2012 unset( $f, $val );
2013 }
2014
2015 // Format event
2016 $atts['event'] = array( 'create' );
2017 if ( isset( $notification['update_email'] ) && 1 == $notification['update_email'] ) {
2018 $atts['event'][] = 'update';
2019 } elseif ( isset( $notification['update_email'] ) && 2 == $notification['update_email'] ) {
2020 $atts['event'] = array( 'update' );
2021 }
2022 }
2023
2024 private static function format_email_to_data( &$atts, $notification ) {
2025 if ( isset( $notification['email_to'] ) ) {
2026 $atts['email_to'] = preg_split( '/ (,|;) /', $notification['email_to'] );
2027 } else {
2028 $atts['email_to'] = array();
2029 }
2030
2031 if ( isset( $notification['also_email_to'] ) ) {
2032 $email_fields = (array) $notification['also_email_to'];
2033 $atts['email_to'] = array_merge( $email_fields, $atts['email_to'] );
2034 unset( $email_fields );
2035 }
2036
2037 foreach ( $atts['email_to'] as $key => $email_field ) {
2038
2039 if ( is_numeric( $email_field ) ) {
2040 $atts['email_to'][ $key ] = '[' . $email_field . ']';
2041 }
2042
2043 if ( strpos( $email_field, '|' ) ) {
2044 $email_opt = explode( '|', $email_field );
2045 if ( isset( $email_opt[0] ) ) {
2046 $atts['email_to'][ $key ] = '[' . $email_opt[0] . ' show=' . $email_opt[1] . ']';
2047 }
2048 unset( $email_opt );
2049 }
2050 }
2051 $atts['email_to'] = implode( ', ', $atts['email_to'] );
2052 }
2053
2054 private static function setup_new_notification( &$new_notification, $notification, $atts ) {
2055 // Set up new notification
2056 $new_notification = array(
2057 'post_content' => array(
2058 'email_to' => $atts['email_to'],
2059 'event' => $atts['event'],
2060 ),
2061 'post_name' => $atts['form_id'] . '_email_' . $atts['email_key'],
2062 );
2063
2064 // Add more fields to the new notification
2065 $add_fields = array( 'email_message', 'email_subject', 'plain_text', 'inc_user_info', 'conditions' );
2066 foreach ( $add_fields as $add_field ) {
2067 if ( isset( $notification[ $add_field ] ) ) {
2068 $new_notification['post_content'][ $add_field ] = $notification[ $add_field ];
2069 } elseif ( in_array( $add_field, array( 'plain_text', 'inc_user_info' ) ) ) {
2070 $new_notification['post_content'][ $add_field ] = 0;
2071 } else {
2072 $new_notification['post_content'][ $add_field ] = '';
2073 }
2074 unset( $add_field );
2075 }
2076
2077 // Set reply to
2078 $new_notification['post_content']['reply_to'] = $atts['reply_to'];
2079
2080 // Set from
2081 if ( ! empty( $atts['reply_to'] ) || ! empty( $atts['reply_to_name'] ) ) {
2082 $new_notification['post_content']['from'] = ( empty( $atts['reply_to_name'] ) ? '[sitename]' : $atts['reply_to_name'] ) . ' <' . ( empty( $atts['reply_to'] ) ? '[admin_email]' : $atts['reply_to'] ) . '>';
2083 }
2084 }
2085
2086 /**
2087 * Switch field IDs in pre-2.0 email conditional logic
2088 *
2089 * @param array $post_content Pass by reference.
2090 */
2091 private static function switch_email_condition_field_ids( &$post_content ) {
2092 // Switch field IDs in conditional logic
2093 if ( isset( $post_content['conditions'] ) && is_array( $post_content['conditions'] ) ) {
2094 foreach ( $post_content['conditions'] as $email_key => $val ) {
2095 if ( is_numeric( $email_key ) ) {
2096 $post_content['conditions'][ $email_key ] = self::switch_action_field_ids( $val, array( 'hide_field' ) );
2097 }
2098 unset( $email_key, $val );
2099 }
2100 }
2101 }
2102
2103 private static function migrate_autoresponder_to_action( $form_options, $form_id, &$notifications ) {
2104 if ( isset( $form_options['auto_responder'] ) && $form_options['auto_responder'] && isset( $form_options['ar_email_message'] ) && $form_options['ar_email_message'] ) {
2105 // migrate autoresponder
2106
2107 $email_field = isset( $form_options['ar_email_to'] ) ? $form_options['ar_email_to'] : 0;
2108 if ( strpos( $email_field, '|' ) ) {
2109 // data from entries field
2110 $email_field = explode( '|', $email_field );
2111 if ( isset( $email_field[1] ) ) {
2112 $email_field = $email_field[1];
2113 }
2114 }
2115 if ( is_numeric( $email_field ) && ! empty( $email_field ) ) {
2116 $email_field = '[' . $email_field . ']';
2117 }
2118
2119 $notification = $form_options;
2120 $new_notification2 = array(
2121 'post_content' => array(
2122 'email_message' => $notification['ar_email_message'],
2123 'email_subject' => isset( $notification['ar_email_subject'] ) ? $notification['ar_email_subject'] : '',
2124 'email_to' => $email_field,
2125 'plain_text' => isset( $notification['ar_plain_text'] ) ? $notification['ar_plain_text'] : 0,
2126 'inc_user_info' => 0,
2127 ),
2128 'post_name' => $form_id . '_email_' . count( $notifications ),
2129 );
2130
2131 $reply_to = isset( $notification['ar_reply_to'] ) ? $notification['ar_reply_to'] : '';
2132 $reply_to_name = isset( $notification['ar_reply_to_name'] ) ? $notification['ar_reply_to_name'] : '';
2133
2134 if ( ! empty( $reply_to ) ) {
2135 $new_notification2['post_content']['reply_to'] = $reply_to;
2136 }
2137
2138 if ( ! empty( $reply_to ) || ! empty( $reply_to_name ) ) {
2139 $new_notification2['post_content']['from'] = ( empty( $reply_to_name ) ? '[sitename]' : $reply_to_name ) . ' <' . ( empty( $reply_to ) ? '[admin_email]' : $reply_to ) . '>';
2140 }
2141
2142 $notifications[] = $new_notification2;
2143 unset( $new_notification2 );
2144 }//end if
2145 }
2146
2147 /**
2148 * PHP 8 backward compatibility for the libxml_disable_entity_loader function
2149 *
2150 * @param bool $loader
2151 *
2152 * @return bool
2153 */
2154 public static function maybe_libxml_disable_entity_loader( $loader ) {
2155 if ( version_compare( phpversion(), '8.0', '<' ) && function_exists( 'libxml_disable_entity_loader' ) ) {
2156 $loader = libxml_disable_entity_loader( $loader ); // phpcs:disable Generic.PHP.DeprecatedFunctions.Deprecated
2157 }
2158
2159 return $loader;
2160 }
2161
2162 public static function check_if_libxml_disable_entity_loader_exists() {
2163 return version_compare( phpversion(), '8.0', '<' ) && ! function_exists( 'libxml_disable_entity_loader' );
2164 }
2165
2166 /**
2167 * Get the file types allowed for importing.
2168 * This is used in the "accept" attribute for the file upload input on the XML import page.
2169 *
2170 * @since 6.8.3
2171 *
2172 * @return array
2173 */
2174 public static function get_supported_upload_file_types() {
2175 $file_types = array( '.xml' );
2176 if ( FrmAppHelper::pro_is_installed() ) {
2177 // CSV Importing is only available in Pro.
2178 $file_types[] = '.csv';
2179 }
2180 return $file_types;
2181 }
2182 }
2183