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

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