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

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