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

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