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

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

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