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

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