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

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