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

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