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

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