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

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