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

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