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

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