Completed
Push — master ( 0691eb...d055d1 )
by Stephanie
03:44 queued 01:07
created

FrmXMLHelper::remove_defaults()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
nc 3
nop 2
dl 0
loc 11
rs 9.9
c 0
b 0
f 0
1
<?php
2
if ( ! defined( 'ABSPATH' ) ) {
3
	die( 'You are not allowed to call this page directly.' );
4
}
5
6
class FrmXMLHelper {
7
8
	public static function get_xml_values( $opt, $padding ) {
9
		if ( is_array( $opt ) ) {
10
			foreach ( $opt as $ok => $ov ) {
11
				echo "\n" . esc_html( $padding );
12
				$tag = ( is_numeric( $ok ) ? 'key:' : '' ) . $ok;
13
				echo '<' . esc_html( $tag ) . '>';
14
				self::get_xml_values( $ov, $padding . '    ' );
15
				if ( is_array( $ov ) ) {
16
					echo "\n" . esc_html( $padding );
17
				}
18
				echo '</' . esc_html( $tag ) . '>';
19
			}
20
		} else {
21
			echo self::cdata( $opt ); // WPCS: XSS ok.
22
		}
23
	}
24
25
	public static function import_xml( $file ) {
26
		if ( ! defined( 'WP_IMPORTING' ) ) {
27
			define( 'WP_IMPORTING', true );
28
		}
29
30
		if ( ! class_exists( 'DOMDocument' ) ) {
31
			return new WP_Error( 'SimpleXML_parse_error', __( 'Your server does not have XML enabled', 'formidable' ), libxml_get_errors() );
32
		}
33
34
		$dom     = new DOMDocument();
35
		$success = $dom->loadXML( file_get_contents( $file ) );
36
		if ( ! $success ) {
37
			return new WP_Error( 'SimpleXML_parse_error', __( 'There was an error when reading this XML file', 'formidable' ), libxml_get_errors() );
38
		}
39
40
		if ( ! function_exists( 'simplexml_import_dom' ) ) {
41
			return new WP_Error( 'SimpleXML_parse_error', __( 'Your server is missing the simplexml_import_dom function', 'formidable' ), libxml_get_errors() );
42
		}
43
44
		$xml = simplexml_import_dom( $dom );
45
		unset( $dom );
46
47
		// halt if loading produces an error
48
		if ( ! $xml ) {
49
			return new WP_Error( 'SimpleXML_parse_error', __( 'There was an error when reading this XML file', 'formidable' ), libxml_get_errors() );
50
		}
51
52
		return self::import_xml_now( $xml );
53
	}
54
55
	/**
56
	 * Add terms, forms (form and field ids), posts (post ids), and entries to db, in that order
57
	 *
58
	 * @since 3.06
59
	 * @return array The number of items imported
60
	 */
61
	public static function import_xml_now( $xml ) {
62
		$imported = self::pre_import_data();
63
64
		foreach ( array( 'term', 'form', 'view' ) as $item_type ) {
65
			// Grab cats, tags, and terms, or forms or posts.
66
			if ( isset( $xml->{$item_type} ) ) {
67
				$function_name = 'import_xml_' . $item_type . 's';
68
				$imported      = self::$function_name( $xml->{$item_type}, $imported );
69
				unset( $function_name, $xml->{$item_type} );
70
			}
71
		}
72
73
		return apply_filters( 'frm_importing_xml', $imported, $xml );
74
	}
75
76
	/**
77
	 * @since 3.06
78
	 * @return array
79
	 */
80
	private static function pre_import_data() {
81
		$defaults = array(
82
			'forms'   => 0,
83
			'fields'  => 0,
84
			'terms'   => 0,
85
			'posts'   => 0,
86
			'views'   => 0,
87
			'actions' => 0,
88
			'styles'  => 0,
89
		);
90
91
		return array(
92
			'imported' => $defaults,
93
			'updated'  => $defaults,
94
			'forms'    => array(),
95
			'terms'    => array(),
96
		);
97
	}
98
99
	public static function import_xml_terms( $terms, $imported ) {
100
		foreach ( $terms as $t ) {
101
			if ( term_exists( (string) $t->term_slug, (string) $t->term_taxonomy ) ) {
102
				continue;
103
			}
104
105
			$parent = self::get_term_parent_id( $t );
106
107
			$term = wp_insert_term(
108
				(string) $t->term_name,
109
				(string) $t->term_taxonomy,
110
				array(
111
					'slug'        => (string) $t->term_slug,
112
					'description' => (string) $t->term_description,
113
					'parent'      => empty( $parent ) ? 0 : $parent,
114
				)
115
			);
116
117
			if ( $term && is_array( $term ) ) {
118
				$imported['imported']['terms'] ++;
119
				$imported['terms'][ (int) $t->term_id ] = $term['term_id'];
120
			}
121
122
			unset( $term, $t );
123
		}
124
125
		return $imported;
126
	}
127
128
	/**
129
	 * @since 2.0.8
130
	 */
131
	private static function get_term_parent_id( $t ) {
132
		$parent = (string) $t->term_parent;
133
		if ( ! empty( $parent ) ) {
134
			$parent = term_exists( (string) $t->term_parent, (string) $t->term_taxonomy );
135
			if ( $parent ) {
136
				$parent = $parent['term_id'];
137
			} else {
138
				$parent = 0;
139
			}
140
		}
141
142
		return $parent;
143
	}
144
145
	public static function import_xml_forms( $forms, $imported ) {
146
		$child_forms = array();
147
148
		// Import child forms first
149
		self::put_child_forms_first( $forms );
150
151
		foreach ( $forms as $item ) {
152
			$form = self::fill_form( $item );
153
154
			self::update_custom_style_setting_on_import( $form );
155
156
			$this_form = self::maybe_get_form( $form );
157
158
			$old_id      = false;
159
			$form_fields = false;
160
			if ( ! empty( $this_form ) ) {
161
				$form_id = $this_form->id;
162
				$old_id  = $this_form->id;
163
				self::update_form( $this_form, $form, $imported );
164
165
				$form_fields = self::get_form_fields( $form_id );
166
			} else {
167
				$form_id = FrmForm::create( $form );
168
				if ( $form_id ) {
169
					if ( empty( $form['parent_form_id'] ) ) {
170
						// Don't include the repeater form in the imported count.
171
						$imported['imported']['forms'] ++;
172
					}
173
174
					// Keep track of whether this specific form was updated or not.
175
					$imported['form_status'][ $form_id ] = 'imported';
176
					self::track_imported_child_forms( (int) $form_id, $form['parent_form_id'], $child_forms );
177
				}
178
			}
179
180
			self::import_xml_fields( $item->field, $form_id, $this_form, $form_fields, $imported );
181
182
			self::delete_removed_fields( $form_fields );
183
184
			// Update field ids/keys to new ones.
185
			do_action( 'frm_after_duplicate_form', $form_id, $form, array( 'old_id' => $old_id ) );
186
187
			$imported['forms'][ (int) $item->id ] = $form_id;
188
189
			// Send pre 2.0 form options through function that creates actions.
190
			self::migrate_form_settings_to_actions( $form['options'], $form_id, $imported, true );
191
192
			do_action( 'frm_after_import_form', $form_id, $form );
193
194
			unset( $form, $item );
195
		}
196
197
		self::maybe_update_child_form_parent_id( $imported['forms'], $child_forms );
198
199
		return $imported;
200
	}
201
202
	private static function fill_form( $item ) {
203
		$form = array(
204
			'id'             => (int) $item->id,
205
			'form_key'       => (string) $item->form_key,
206
			'name'           => (string) $item->name,
207
			'description'    => (string) $item->description,
208
			'options'        => (string) $item->options,
209
			'logged_in'      => (int) $item->logged_in,
210
			'is_template'    => (int) $item->is_template,
211
			'editable'       => (int) $item->editable,
212
			'status'         => (string) $item->status,
213
			'parent_form_id' => isset( $item->parent_form_id ) ? (int) $item->parent_form_id : 0,
214
			'created_at'     => gmdate( 'Y-m-d H:i:s', strtotime( (string) $item->created_at ) ),
215
		);
216
217
		if ( empty( $item->created_at ) ) {
218
			$form['created_at'] = current_time( 'mysql', 1 );
219
		}
220
221
		$form['options'] = FrmAppHelper::maybe_json_decode( $form['options'] );
222
223
		return $form;
224
	}
225
226
	private static function maybe_get_form( $form ) {
227
		// if template, allow to edit if form keys match, otherwise, creation date must also match
228
		$edit_query = array(
229
			'form_key'    => $form['form_key'],
230
			'is_template' => $form['is_template'],
231
		);
232
		if ( ! $form['is_template'] ) {
233
			$edit_query['created_at'] = $form['created_at'];
234
		}
235
236
		$edit_query = apply_filters( 'frm_match_xml_form', $edit_query, $form );
237
238
		return FrmForm::getAll( $edit_query, '', 1 );
239
	}
240
241
	private static function update_form( $this_form, $form, &$imported ) {
242
		$form_id = $this_form->id;
243
		FrmForm::update( $form_id, $form );
244
		if ( empty( $form['parent_form_id'] ) ) {
245
			// Don't include the repeater form in the updated count.
246
			$imported['updated']['forms'] ++;
247
		}
248
249
		// Keep track of whether this specific form was updated or not
250
		$imported['form_status'][ $form_id ] = 'updated';
251
	}
252
253
	private static function get_form_fields( $form_id ) {
254
		$form_fields = FrmField::get_all_for_form( $form_id, '', 'exclude', 'exclude' );
255
		$old_fields  = array();
256
		foreach ( $form_fields as $f ) {
257
			$old_fields[ $f->id ]        = $f;
258
			$old_fields[ $f->field_key ] = $f->id;
259
			unset( $f );
260
		}
261
		$form_fields = $old_fields;
262
263
		return $form_fields;
264
	}
265
266
	/**
267
	 * Delete any fields attached to this form that were not included in the template
268
	 */
269
	private static function delete_removed_fields( $form_fields ) {
270
		if ( ! empty( $form_fields ) ) {
271
			foreach ( $form_fields as $field ) {
272
				if ( is_object( $field ) ) {
273
					FrmField::destroy( $field->id );
274
				}
275
				unset( $field );
276
			}
277
		}
278
	}
279
280
	/**
281
	 * Put child forms first so they will be imported before parents
282
	 *
283
	 * @since 2.0.16
284
	 *
285
	 * @param array $forms
286
	 */
287
	private static function put_child_forms_first( &$forms ) {
288
		$child_forms   = array();
289
		$regular_forms = array();
290
291
		foreach ( $forms as $form ) {
292
			$parent_form_id = isset( $form->parent_form_id ) ? (int) $form->parent_form_id : 0;
293
294
			if ( $parent_form_id ) {
295
				$child_forms[] = $form;
296
			} else {
297
				$regular_forms[] = $form;
298
			}
299
		}
300
301
		$forms = array_merge( $child_forms, $regular_forms );
302
	}
303
304
	/**
305
	 * Keep track of all imported child forms
306
	 *
307
	 * @since 2.0.16
308
	 *
309
	 * @param int $form_id
310
	 * @param int $parent_form_id
311
	 * @param array $child_forms
312
	 */
313
	private static function track_imported_child_forms( $form_id, $parent_form_id, &$child_forms ) {
314
		if ( $parent_form_id ) {
315
			$child_forms[ $form_id ] = $parent_form_id;
316
		}
317
	}
318
319
	/**
320
	 * Update the parent_form_id on imported child forms
321
	 * Child forms are imported first so their parent_form_id will need to be updated after the parent is imported
322
	 *
323
	 * @since 2.0.6
324
	 *
325
	 * @param array $imported_forms
326
	 * @param array $child_forms
327
	 */
328
	private static function maybe_update_child_form_parent_id( $imported_forms, $child_forms ) {
329
		foreach ( $child_forms as $child_form_id => $old_parent_form_id ) {
330
331
			if ( isset( $imported_forms[ $old_parent_form_id ] ) && $imported_forms[ $old_parent_form_id ] != $old_parent_form_id ) {
332
				// Update all children with this old parent_form_id
333
				$new_parent_form_id = (int) $imported_forms[ $old_parent_form_id ];
334
335
				FrmForm::update( $child_form_id, array( 'parent_form_id' => $new_parent_form_id ) );
336
			}
337
		}
338
	}
339
340
	/**
341
	 * Import all fields for a form
342
	 *
343
	 * @since 2.0.13
344
	 *
345
	 * TODO: Cut down on params
346
	 */
347
	private static function import_xml_fields( $xml_fields, $form_id, $this_form, &$form_fields, &$imported ) {
348
		$in_section = 0;
349
350
		foreach ( $xml_fields as $field ) {
351
			$f = self::fill_field( $field, $form_id );
352
353
			self::set_default_value( $f );
354
			self::maybe_add_required( $f );
355
			self::maybe_update_in_section_variable( $in_section, $f );
356
			self::maybe_update_form_select( $f, $imported );
357
			self::maybe_update_get_values_form_setting( $imported, $f );
358
			self::migrate_placeholders( $f );
359
360
			if ( ! empty( $this_form ) ) {
361
				// check for field to edit by field id
362
				if ( isset( $form_fields[ $f['id'] ] ) ) {
363
					FrmField::update( $f['id'], $f );
364
					$imported['updated']['fields'] ++;
365
366
					unset( $form_fields[ $f['id'] ] );
367
368
					//unset old field key
369
					if ( isset( $form_fields[ $f['field_key'] ] ) ) {
370
						unset( $form_fields[ $f['field_key'] ] );
371
					}
372
				} elseif ( isset( $form_fields[ $f['field_key'] ] ) ) {
373
					// check for field to edit by field key
374
					unset( $f['id'] );
375
376
					FrmField::update( $form_fields[ $f['field_key'] ], $f );
377
					$imported['updated']['fields'] ++;
378
379
					unset( $form_fields[ $form_fields[ $f['field_key'] ] ] ); //unset old field id
380
					unset( $form_fields[ $f['field_key'] ] ); //unset old field key
381
				} else {
382
					// if no matching field id or key in this form, create the field
383
					self::create_imported_field( $f, $imported );
384
				}
385
			} else {
386
387
				self::create_imported_field( $f, $imported );
388
			}
389
		}
390
	}
391
392
	private static function fill_field( $field, $form_id ) {
393
		return array(
394
			'id'            => (int) $field->id,
395
			'field_key'     => (string) $field->field_key,
396
			'name'          => (string) $field->name,
397
			'description'   => (string) $field->description,
398
			'type'          => (string) $field->type,
399
			'default_value' => FrmAppHelper::maybe_json_decode( (string) $field->default_value ),
400
			'field_order'   => (int) $field->field_order,
401
			'form_id'       => (int) $form_id,
402
			'required'      => (int) $field->required,
403
			'options'       => FrmAppHelper::maybe_json_decode( (string) $field->options ),
404
			'field_options' => FrmAppHelper::maybe_json_decode( (string) $field->field_options ),
405
		);
406
	}
407
408
	/**
409
	 * @since 4.06
410
	 */
411
	private static function set_default_value( &$f ) {
412
		$has_default = array(
413
			'text',
414
			'email',
415
			'url',
416
			'textarea',
417
			'number',
418
			'phone',
419
			'date',
420
			'hidden',
421
			'password',
422
			'tag',
423
		);
424
425
		if ( is_array( $f['default_value'] ) && in_array( $f['type'], $has_default, true ) ) {
426
			if ( count( $f['default_value'] ) === 1 ) {
427
				$f['default_value'] = '[' . reset( $f['default_value'] ) . ']';
428
			} else {
429
				$f['default_value'] = reset( $f['default_value'] );
430
			}
431
		}
432
	}
433
434
	/**
435
	 * Make sure the required indicator is set.
436
	 *
437
	 * @since 4.05
438
	 */
439
	private static function maybe_add_required( &$f ) {
440
		if ( $f['required'] && ! isset( $f['field_options']['required_indicator'] ) ) {
441
			$f['field_options']['required_indicator'] = '*';
442
		}
443
	}
444
445
	/**
446
	 * Update the current in_section value
447
	 *
448
	 * @since 2.0.25
449
	 *
450
	 * @param int $in_section
451
	 * @param array $f
452
	 */
453
	private static function maybe_update_in_section_variable( &$in_section, &$f ) {
454
		// If we're at the end of a section, switch $in_section is 0
455
		if ( in_array( $f['type'], array( 'end_divider', 'break', 'form' ) ) ) {
456
			$in_section = 0;
457
		}
458
459
		// Update the current field's in_section value
460
		if ( ! isset( $f['field_options']['in_section'] ) ) {
461
			$f['field_options']['in_section'] = $in_section;
462
		}
463
464
		// If we're starting a new section, switch $in_section to ID of divider
465
		if ( $f['type'] == 'divider' ) {
466
			$in_section = $f['id'];
467
		}
468
	}
469
470
	/**
471
	 * Switch the form_select on a repeating field or embedded form if it needs to be switched
472
	 *
473
	 * @since 2.0.16
474
	 *
475
	 * @param array $f
476
	 * @param array $imported
477
	 */
478
	private static function maybe_update_form_select( &$f, $imported ) {
479
		if ( ! isset( $imported['forms'] ) ) {
480
			return;
481
		}
482
483
		if ( $f['type'] == 'form' || ( $f['type'] == 'divider' && FrmField::is_option_true( $f['field_options'], 'repeat' ) ) ) {
484 View Code Duplication
			if ( FrmField::is_option_true( $f['field_options'], 'form_select' ) ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
485
				$form_select = (int) $f['field_options']['form_select'];
486
				if ( isset( $imported['forms'][ $form_select ] ) ) {
487
					$f['field_options']['form_select'] = $imported['forms'][ $form_select ];
488
				}
489
			}
490
		}
491
	}
492
493
	/**
494
	 * Update the get_values_form setting if the form was imported
495
	 *
496
	 * @since 2.01.0
497
	 *
498
	 * @param array $imported
499
	 * @param array $f
500
	 */
501
	private static function maybe_update_get_values_form_setting( $imported, &$f ) {
502
		if ( ! isset( $imported['forms'] ) ) {
503
			return;
504
		}
505
506 View Code Duplication
		if ( FrmField::is_option_true_in_array( $f['field_options'], 'get_values_form' ) ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
507
			$old_form = $f['field_options']['get_values_form'];
508
			if ( isset( $imported['forms'][ $old_form ] ) ) {
509
				$f['field_options']['get_values_form'] = $imported['forms'][ $old_form ];
510
			}
511
		}
512
	}
513
514
	/**
515
	 * If field settings have been migrated, update the values during import.
516
	 *
517
	 * @since 4.0
518
	 */
519
	private static function run_field_migrations( &$f ) {
520
		self::migrate_placeholders( $f );
521
		$f = apply_filters( 'frm_import_xml_field', $f );
522
	}
523
524
	/**
525
	 * @since 4.0
526
	 */
527
	private static function migrate_placeholders( &$f ) {
528
		$update_values = self::migrate_field_placeholder( $f, 'clear_on_focus' );
529
		foreach ( $update_values as $k => $v ) {
530
			$f[ $k ] = $v;
531
		}
532
533
		$update_values = self::migrate_field_placeholder( $f, 'default_blank' );
534
		foreach ( $update_values as $k => $v ) {
535
			$f[ $k ] = $v;
536
		}
537
	}
538
539
	/**
540
	 * Move clear_on_focus or default_blank to placeholder.
541
	 * Also called during database migration in FrmMigrate.
542
	 *
543
	 * @since 4.0
544
	 * @return array
545
	 */
546
	public static function migrate_field_placeholder( $field, $type ) {
547
		$field = (array) $field;
548
		$field_options = $field['field_options'];
549
		if ( empty( $field_options[ $type ] ) || empty( $field['default_value'] ) ) {
550
			return array();
551
		}
552
553
		$field_options['placeholder'] = is_array( $field['default_value'] ) ? reset( $field['default_value'] ) : $field['default_value'];
554
		unset( $field_options['default_blank'], $field_options['clear_on_focus'] );
555
556
		$changes = array(
557
			'field_options' => $field_options,
558
			'default_value' => '',
559
		);
560
561
		// If a dropdown placeholder was used, remove the option so it won't be included twice.
562
		$options = $field['options'];
563
		if ( $type === 'default_blank' && is_array( $options ) ) {
564
			$default_value = $field['default_value'];
565
			if ( is_array( $default_value ) ) {
566
				$default_value = reset( $default_value );
567
			}
568
569
			foreach ( $options as $opt_key => $opt ) {
570
				if ( is_array( $opt ) ) {
571
					$opt = isset( $opt['value'] ) ? $opt['value'] : ( isset( $opt['label'] ) ? $opt['label'] : reset( $opt ) );
572
				}
573
574
				if ( $opt == $default_value ) {
575
					unset( $options[ $opt_key ] );
576
					break;
577
				}
578
			}
579
			$changes['options'] = $options;
580
		}
581
582
		return $changes;
583
	}
584
585
	/**
586
	 * Create an imported field
587
	 *
588
	 * @since 2.0.25
589
	 *
590
	 * @param array $f
591
	 * @param array $imported
592
	 */
593
	private static function create_imported_field( $f, &$imported ) {
594
		$defaults           = self::default_field_options( $f['type'] );
595
		$f['field_options'] = array_merge( $defaults, $f['field_options'] );
596
597
		$new_id = FrmField::create( $f );
598
		if ( $new_id != false ) {
599
			$imported['imported']['fields'] ++;
600
			do_action( 'frm_after_field_is_imported', $f, $new_id );
601
		}
602
	}
603
604
	/**
605
	 * Updates the custom style setting on import
606
	 * Convert the post slug to an ID
607
	 *
608
	 * @since 2.0.19
609
	 *
610
	 * @param array $form
611
	 */
612
	private static function update_custom_style_setting_on_import( &$form ) {
613
		if ( ! isset( $form['options']['custom_style'] ) ) {
614
			return;
615
		}
616
617
		if ( is_numeric( $form['options']['custom_style'] ) ) {
618
			// Set to default
619
			$form['options']['custom_style'] = 1;
620
		} else {
621
			// Replace the style name with the style ID on import
622
			global $wpdb;
623
			$table    = $wpdb->prefix . 'posts';
624
			$where    = array(
625
				'post_name' => $form['options']['custom_style'],
626
				'post_type' => 'frm_styles',
627
			);
628
			$select   = 'ID';
629
			$style_id = FrmDb::get_var( $table, $where, $select );
630
631
			if ( $style_id ) {
632
				$form['options']['custom_style'] = $style_id;
633
			} else {
634
				// save the old style to maybe update after styles import
635
				$form['options']['old_style'] = $form['options']['custom_style'];
636
637
				// Set to default
638
				$form['options']['custom_style'] = 1;
639
			}
640
		}
641
	}
642
643
	/**
644
	 * After styles are imported, check for any forms that were linked
645
	 * and link them back up.
646
	 *
647
	 * @since 2.2.7
648
	 */
649
	private static function update_custom_style_setting_after_import( $form_id ) {
650
		$form = FrmForm::getOne( $form_id );
651
652
		if ( $form && isset( $form->options['old_style'] ) ) {
653
			$form                            = (array) $form;
654
			$saved_style                     = $form['options']['custom_style'];
655
			$form['options']['custom_style'] = $form['options']['old_style'];
656
			self::update_custom_style_setting_on_import( $form );
657
			$has_changed = ( $form['options']['custom_style'] != $saved_style && $form['options']['custom_style'] != $form['options']['old_style'] );
658
			if ( $has_changed ) {
659
				FrmForm::update( $form['id'], $form );
660
			}
661
		}
662
	}
663
664
	public static function import_xml_views( $views, $imported ) {
665
		$imported['posts'] = array();
666
		$form_action_type  = FrmFormActionsController::$action_post_type;
667
668
		$post_types = array(
669
			'frm_display'     => 'views',
670
			$form_action_type => 'actions',
671
			'frm_styles'      => 'styles',
672
		);
673
674
		foreach ( $views as $item ) {
675
			$post = array(
676
				'post_title'     => (string) $item->title,
677
				'post_name'      => (string) $item->post_name,
678
				'post_type'      => (string) $item->post_type,
679
				'post_password'  => (string) $item->post_password,
680
				'guid'           => (string) $item->guid,
681
				'post_status'    => (string) $item->status,
682
				'post_author'    => FrmAppHelper::get_user_id_param( (string) $item->post_author ),
683
				'post_id'        => (int) $item->post_id,
684
				'post_parent'    => (int) $item->post_parent,
685
				'menu_order'     => (int) $item->menu_order,
686
				'post_content'   => FrmFieldsHelper::switch_field_ids( (string) $item->content ),
687
				'post_excerpt'   => FrmFieldsHelper::switch_field_ids( (string) $item->excerpt ),
688
				'is_sticky'      => (string) $item->is_sticky,
689
				'comment_status' => (string) $item->comment_status,
690
				'post_date'      => (string) $item->post_date,
691
				'post_date_gmt'  => (string) $item->post_date_gmt,
692
				'ping_status'    => (string) $item->ping_status,
693
				'postmeta'       => array(),
694
				'tax_input'      => array(),
695
			);
696
697
			$old_id = $post['post_id'];
698
			self::populate_post( $post, $item, $imported );
699
700
			unset( $item );
701
702
			$post_id = false;
703
			if ( $post['post_type'] == $form_action_type ) {
704
				$action_control = FrmFormActionsController::get_form_actions( $post['post_excerpt'] );
705
				if ( $action_control && is_object( $action_control ) ) {
706
					$post_id = $action_control->maybe_create_action( $post, $imported['form_status'] );
707
				}
708
				unset( $action_control );
709
			} elseif ( $post['post_type'] == 'frm_styles' ) {
710
				// Properly encode post content before inserting the post
711
				$post['post_content'] = FrmAppHelper::maybe_json_decode( $post['post_content'] );
712
				$custom_css           = isset( $post['post_content']['custom_css'] ) ? $post['post_content']['custom_css'] : '';
713
				$post['post_content'] = FrmAppHelper::prepare_and_encode( $post['post_content'] );
714
715
				// Create/update post now
716
				$post_id = wp_insert_post( $post );
717
				self::maybe_update_custom_css( $custom_css );
718
			} else {
719
				// Create/update post now
720
				$post_id = wp_insert_post( $post );
721
			}
722
723
			if ( ! is_numeric( $post_id ) ) {
724
				continue;
725
			}
726
727
			self::update_postmeta( $post, $post_id );
728
729
			$this_type = 'posts';
730
			if ( isset( $post_types[ $post['post_type'] ] ) ) {
731
				$this_type = $post_types[ $post['post_type'] ];
732
			}
733
734
			if ( isset( $post['ID'] ) && $post_id == $post['ID'] ) {
735
				$imported['updated'][ $this_type ] ++;
736
			} else {
737
				$imported['imported'][ $this_type ] ++;
738
			}
739
740
			$imported['posts'][ (int) $old_id ] = $post_id;
741
742
			do_action( 'frm_after_import_view', $post_id, $post );
743
744
			unset( $post );
745
		}
746
747
		self::maybe_update_stylesheet( $imported );
748
749
		return $imported;
750
	}
751
752
	private static function populate_post( &$post, $item, $imported ) {
753
		if ( isset( $item->attachment_url ) ) {
754
			$post['attachment_url'] = (string) $item->attachment_url;
755
		}
756
757
		if ( $post['post_type'] == FrmFormActionsController::$action_post_type && isset( $imported['forms'][ (int) $post['menu_order'] ] ) ) {
758
			// update to new form id
759
			$post['menu_order'] = $imported['forms'][ (int) $post['menu_order'] ];
760
		}
761
762
		// Don't allow default styles to take over a site's default style
763
		if ( 'frm_styles' == $post['post_type'] ) {
764
			$post['menu_order'] = 0;
765
		}
766
767
		foreach ( $item->postmeta as $meta ) {
768
			self::populate_postmeta( $post, $meta, $imported );
769
			unset( $meta );
770
		}
771
772
		self::populate_taxonomies( $post, $item );
773
774
		self::maybe_editing_post( $post );
775
	}
776
777
	private static function populate_postmeta( &$post, $meta, $imported ) {
778
		global $frm_duplicate_ids;
779
780
		$m = array(
781
			'key'   => (string) $meta->meta_key,
782
			'value' => (string) $meta->meta_value,
783
		);
784
785
		//switch old form and field ids to new ones
786
		if ( $m['key'] == 'frm_form_id' && isset( $imported['forms'][ (int) $m['value'] ] ) ) {
787
			$m['value'] = $imported['forms'][ (int) $m['value'] ];
788
		} else {
789
			$m['value'] = FrmAppHelper::maybe_json_decode( $m['value'] );
790
791
			if ( ! empty( $frm_duplicate_ids ) ) {
792
793
				if ( $m['key'] == 'frm_dyncontent' ) {
794
					$m['value'] = FrmFieldsHelper::switch_field_ids( $m['value'] );
795
				} elseif ( $m['key'] == 'frm_options' ) {
796
797
					foreach ( array( 'date_field_id', 'edate_field_id' ) as $setting_name ) {
798
						if ( isset( $m['value'][ $setting_name ] ) && is_numeric( $m['value'][ $setting_name ] ) && isset( $frm_duplicate_ids[ $m['value'][ $setting_name ] ] ) ) {
799
							$m['value'][ $setting_name ] = $frm_duplicate_ids[ $m['value'][ $setting_name ] ];
800
						}
801
					}
802
803
					$check_dup_array = array();
804
					if ( isset( $m['value']['order_by'] ) && ! empty( $m['value']['order_by'] ) ) {
805
						if ( is_numeric( $m['value']['order_by'] ) && isset( $frm_duplicate_ids[ $m['value']['order_by'] ] ) ) {
806
							$m['value']['order_by'] = $frm_duplicate_ids[ $m['value']['order_by'] ];
807
						} elseif ( is_array( $m['value']['order_by'] ) ) {
808
							$check_dup_array[] = 'order_by';
809
						}
810
					}
811
812
					if ( isset( $m['value']['where'] ) && ! empty( $m['value']['where'] ) ) {
813
						$check_dup_array[] = 'where';
814
					}
815
816
					foreach ( $check_dup_array as $check_k ) {
817
						foreach ( (array) $m['value'][ $check_k ] as $mk => $mv ) {
818
							if ( isset( $frm_duplicate_ids[ $mv ] ) ) {
819
								$m['value'][ $check_k ][ $mk ] = $frm_duplicate_ids[ $mv ];
820
							}
821
							unset( $mk, $mv );
822
						}
823
					}
824
				}
825
			}
826
		}
827
828
		if ( ! is_array( $m['value'] ) ) {
829
			$m['value'] = FrmAppHelper::maybe_json_decode( $m['value'] );
830
		}
831
832
		$post['postmeta'][ (string) $meta->meta_key ] = $m['value'];
833
	}
834
835
	/**
836
	 * Add terms to post
837
	 *
838
	 * @param array $post by reference
839
	 * @param object $item The XML object data
840
	 */
841
	private static function populate_taxonomies( &$post, $item ) {
842
		foreach ( $item->category as $c ) {
843
			$att = $c->attributes();
844
			if ( ! isset( $att['nicename'] ) ) {
845
				continue;
846
			}
847
848
			$taxonomy = (string) $att['domain'];
849
			if ( is_taxonomy_hierarchical( $taxonomy ) ) {
850
				$name   = (string) $att['nicename'];
851
				$h_term = get_term_by( 'slug', $name, $taxonomy );
852
				if ( $h_term ) {
853
					$name = $h_term->term_id;
854
				}
855
				unset( $h_term );
856
			} else {
857
				$name = (string) $c;
858
			}
859
860
			if ( ! isset( $post['tax_input'][ $taxonomy ] ) ) {
861
				$post['tax_input'][ $taxonomy ] = array();
862
			}
863
864
			$post['tax_input'][ $taxonomy ][] = $name;
865
			unset( $name );
866
		}
867
	}
868
869
	/**
870
	 * Edit post if the key and created time match
871
	 */
872
	private static function maybe_editing_post( &$post ) {
873
		$match_by = array(
874
			'post_type'      => $post['post_type'],
875
			'name'           => $post['post_name'],
876
			'post_status'    => $post['post_status'],
877
			'posts_per_page' => 1,
878
		);
879
880
		if ( in_array( $post['post_status'], array( 'trash', 'draft' ) ) ) {
881
			$match_by['include'] = $post['post_id'];
882
			unset( $match_by['name'] );
883
		}
884
885
		$editing = get_posts( $match_by );
886
887
		if ( ! empty( $editing ) && current( $editing )->post_date == $post['post_date'] ) {
888
			// set the id of the post to edit
889
			$post['ID'] = current( $editing )->ID;
890
		}
891
	}
892
893
	private static function update_postmeta( &$post, $post_id ) {
894
		foreach ( $post['postmeta'] as $k => $v ) {
895
			if ( '_edit_last' == $k ) {
896
				$v = FrmAppHelper::get_user_id_param( $v );
897
			} elseif ( '_thumbnail_id' == $k && FrmAppHelper::pro_is_installed() ) {
898
				// Change the attachment ID.
899
				$field_obj = FrmFieldFactory::get_field_type( 'file' );
900
				$v         = $field_obj->get_file_id( $v );
901
			}
902
903
			update_post_meta( $post_id, $k, $v );
904
905
			unset( $k, $v );
906
		}
907
	}
908
909
	/**
910
	 * If a template includes custom css, let's include it.
911
	 * The custom css is included on the default style.
912
	 *
913
	 * @since 2.03
914
	 */
915
	private static function maybe_update_custom_css( $custom_css ) {
916
		if ( empty( $custom_css ) ) {
917
			return;
918
		}
919
920
		$frm_style                                 = new FrmStyle();
921
		$default_style                             = $frm_style->get_default_style();
922
		$default_style->post_content['custom_css'] .= "\r\n\r\n" . $custom_css;
923
		$frm_style->save( $default_style );
924
	}
925
926
	private static function maybe_update_stylesheet( $imported ) {
927
		$new_styles     = isset( $imported['imported']['styles'] ) && ! empty( $imported['imported']['styles'] );
928
		$updated_styles = isset( $imported['updated']['styles'] ) && ! empty( $imported['updated']['styles'] );
929
		if ( $new_styles || $updated_styles ) {
930
			if ( is_admin() && function_exists( 'get_filesystem_method' ) ) {
931
				$frm_style = new FrmStyle();
932
				$frm_style->update( 'default' );
933
			}
934
			foreach ( $imported['forms'] as $form_id ) {
935
				self::update_custom_style_setting_after_import( $form_id );
936
			}
937
		}
938
	}
939
940
	/**
941
	 * @param string $message
942
	 */
943
	public static function parse_message( $result, &$message, &$errors ) {
944
		if ( is_wp_error( $result ) ) {
945
			$errors[] = $result->get_error_message();
946
		} elseif ( ! $result ) {
947
			return;
948
		}
949
950
		if ( ! is_array( $result ) ) {
951
			$message = is_string( $result ) ? $result : htmlentities( print_r( $result, 1 ) );
952
953
			return;
954
		}
955
956
		$t_strings = array(
957
			'imported' => __( 'Imported', 'formidable' ),
958
			'updated'  => __( 'Updated', 'formidable' ),
959
		);
960
961
		$message = '<ul>';
962
		foreach ( $result as $type => $results ) {
963
			if ( ! isset( $t_strings[ $type ] ) ) {
964
				// only print imported and updated
965
				continue;
966
			}
967
968
			$s_message = array();
969
			foreach ( $results as $k => $m ) {
970
				self::item_count_message( $m, $k, $s_message );
971
				unset( $k, $m );
972
			}
973
974
			if ( ! empty( $s_message ) ) {
975
				$message .= '<li><strong>' . $t_strings[ $type ] . ':</strong> ';
976
				$message .= implode( ', ', $s_message );
977
				$message .= '</li>';
978
			}
979
		}
980
981
		if ( $message == '<ul>' ) {
982
			$message  = '';
983
			$errors[] = __( 'Nothing was imported or updated', 'formidable' );
984
		} else {
985
			self::add_form_link_to_message( $result, $message );
986
987
			$message .= '</ul>';
988
		}
989
	}
990
991
	public static function item_count_message( $m, $type, &$s_message ) {
992
		if ( ! $m ) {
993
			return;
994
		}
995
996
		$strings = array(
997
			/* translators: %1$s: Number of items */
998
			'forms'   => sprintf( _n( '%1$s Form', '%1$s Forms', $m, 'formidable' ), $m ),
999
			/* translators: %1$s: Number of items */
1000
			'fields'  => sprintf( _n( '%1$s Field', '%1$s Fields', $m, 'formidable' ), $m ),
1001
			/* translators: %1$s: Number of items */
1002
			'items'   => sprintf( _n( '%1$s Entry', '%1$s Entries', $m, 'formidable' ), $m ),
1003
			/* translators: %1$s: Number of items */
1004
			'views'   => sprintf( _n( '%1$s View', '%1$s Views', $m, 'formidable' ), $m ),
1005
			/* translators: %1$s: Number of items */
1006
			'posts'   => sprintf( _n( '%1$s Post', '%1$s Posts', $m, 'formidable' ), $m ),
1007
			/* translators: %1$s: Number of items */
1008
			'styles'  => sprintf( _n( '%1$s Style', '%1$s Styles', $m, 'formidable' ), $m ),
1009
			/* translators: %1$s: Number of items */
1010
			'terms'   => sprintf( _n( '%1$s Term', '%1$s Terms', $m, 'formidable' ), $m ),
1011
			/* translators: %1$s: Number of items */
1012
			'actions' => sprintf( _n( '%1$s Form Action', '%1$s Form Actions', $m, 'formidable' ), $m ),
1013
		);
1014
1015
		$s_message[] = isset( $strings[ $type ] ) ? $strings[ $type ] : ' ' . $m . ' ' . ucfirst( $type );
1016
	}
1017
1018
	/**
1019
	 * If a single form was imported, include a link in the success message.
1020
	 *
1021
	 * @since 4.0
1022
	 * @param array  $result The response from the XML import.
1023
	 * @param string $message The response shown on the page after import.
1024
	 */
1025
	private static function add_form_link_to_message( $result, &$message ) {
1026
		$total_forms = $result['imported']['forms'] + $result['updated']['forms'];
1027
		if ( $total_forms > 1 ) {
1028
			return;
1029
		}
1030
1031
		$primary_form = reset( $result['forms'] );
1032
		if ( ! empty( $primary_form ) ) {
1033
			$primary_form = FrmForm::getOne( $primary_form );
1034
			$form_id      = empty( $primary_form->parent_form_id ) ? $primary_form->id : $primary_form->parent_form_id;
1035
1036
			$message .= '<li><a href="' . esc_url( FrmForm::get_edit_link( $form_id ) ) . '">' . esc_html__( 'Go to imported form', 'formidable' ) . '</a></li>';
1037
		}
1038
	}
1039
1040
	/**
1041
	 * Prepare the form options for export
1042
	 *
1043
	 * @since 2.0.19
1044
	 *
1045
	 * @param string $options
1046
	 *
1047
	 * @return string
1048
	 */
1049
	public static function prepare_form_options_for_export( $options ) {
1050
		FrmAppHelper::unserialize_or_decode( $options );
1051
		// Change custom_style to the post_name instead of ID (1 may be a string)
1052
		$not_default = isset( $options['custom_style'] ) && 1 != $options['custom_style'];
1053
		if ( $not_default ) {
1054
			global $wpdb;
1055
			$table  = $wpdb->prefix . 'posts';
1056
			$where  = array( 'ID' => $options['custom_style'] );
1057
			$select = 'post_name';
1058
1059
			$style_name = FrmDb::get_var( $table, $where, $select );
1060
1061
			if ( $style_name ) {
1062
				$options['custom_style'] = $style_name;
1063
			} else {
1064
				$options['custom_style'] = 1;
1065
			}
1066
		}
1067
		self::remove_default_form_options( $options );
1068
		$options = serialize( $options );
1069
1070
		return self::cdata( $options );
1071
	}
1072
1073
	/**
1074
	 * If the saved value is the same as the default, remove it from the export
1075
	 * This keeps file size down and prevents overriding global settings after import
1076
	 *
1077
	 * @since 3.06
1078
	 */
1079
	private static function remove_default_form_options( &$options ) {
1080
		$defaults = FrmFormsHelper::get_default_opts();
1081
		if ( is_callable( 'FrmProFormsHelper::get_default_opts' ) ) {
1082
			$defaults += FrmProFormsHelper::get_default_opts();
1083
		}
1084
		self::remove_defaults( $defaults, $options );
1085
	}
1086
1087
	/**
1088
	 * Remove extra settings from field to keep file size down
1089
	 *
1090
	 * @since 3.06
1091
	 */
1092
	public static function prepare_field_for_export( &$field ) {
1093
		self::remove_default_field_options( $field );
1094
	}
1095
1096
	/**
1097
	 * Remove defaults from field options too
1098
	 *
1099
	 * @since 3.06
1100
	 */
1101
	private static function remove_default_field_options( &$field ) {
1102
		$defaults = self::default_field_options( $field->type );
1103
		if ( empty( $defaults['blank'] ) ) {
1104
			$global_settings   = new FrmSettings();
1105
			$global_defaults   = $global_settings->default_options();
1106
			$defaults['blank'] = $global_defaults['blank_msg'];
1107
		}
1108
1109
		$options = $field->field_options;
1110
		FrmAppHelper::unserialize_or_decode( $options );
1111
		self::remove_defaults( $defaults, $options );
1112
		self::remove_default_html( 'custom_html', $defaults, $options );
1113
1114
		// Get variations on the defaults.
1115
		if ( isset( $options['invalid'] ) ) {
1116
			$defaults = array(
1117
				/* translators: %s: Field name */
1118
				'invalid' => sprintf( __( '%s is invalid', 'formidable' ), $field->name ),
1119
			);
1120
			self::remove_defaults( $defaults, $options );
1121
		}
1122
1123
		$field->field_options = serialize( $options );
1124
	}
1125
1126
	/**
1127
	 * @since 3.06.03
1128
	 */
1129
	private static function default_field_options( $type ) {
1130
		$defaults = FrmFieldsHelper::get_default_field_options( $type );
1131
		if ( empty( $defaults['custom_html'] ) ) {
1132
			$defaults['custom_html'] = FrmFieldsHelper::get_default_html( $type );
1133
		}
1134
		return $defaults;
1135
	}
1136
1137
	/**
1138
	 * Compare the default array to the saved values and
1139
	 * remove if they are the same
1140
	 *
1141
	 * @since 3.06
1142
	 */
1143
	private static function remove_defaults( $defaults, &$saved ) {
1144
		$array_defaults = array_filter( $defaults, 'is_array' );
1145
		foreach ( $array_defaults as $d => $default ) {
1146
			// compare array defaults
1147
			if ( $default == $saved[ $d ] ) {
1148
				unset( $saved[ $d ] );
1149
			}
1150
			unset( $defaults[ $d ] );
1151
		}
1152
		$saved = array_diff_assoc( (array) $saved, $defaults );
1153
	}
1154
1155
	/**
1156
	 * The line endings may prevent html from being equal when it should
1157
	 *
1158
	 * @since 3.06
1159
	 */
1160
	private static function remove_default_html( $html_name, $defaults, &$options ) {
1161
		if ( ! isset( $options[ $html_name ] ) || ! isset( $defaults[ $html_name ] ) ) {
1162
			return;
1163
		}
1164
1165
		$old_html     = str_replace( "\r\n", "\n", $options[ $html_name ] );
1166
		$default_html = $defaults[ $html_name ];
1167
		if ( $old_html == $default_html ) {
1168
			unset( $options[ $html_name ] );
1169
1170
			return;
1171
		}
1172
1173
		// Account for some of the older field default HTML.
1174
		$default_html = str_replace( ' id="frm_desc_field_[key]"', '', $default_html );
1175
		if ( $old_html == $default_html ) {
1176
			unset( $options[ $html_name ] );
1177
		}
1178
	}
1179
1180
	public static function cdata( $str ) {
1181
		FrmAppHelper::unserialize_or_decode( $str );
1182
		if ( is_array( $str ) ) {
1183
			$str = json_encode( $str );
1184
		} elseif ( seems_utf8( $str ) == false ) {
1185
			$str = utf8_encode( $str );
1186
		}
1187
1188
		if ( is_numeric( $str ) ) {
1189
			return $str;
1190
		}
1191
1192
		self::remove_invalid_characters_from_xml( $str );
1193
1194
		// $str = ent2ncr(esc_html( $str));
1195
		$str = '<![CDATA[' . str_replace( ']]>', ']]]]><![CDATA[>', $str ) . ']]>';
1196
1197
		return $str;
1198
	}
1199
1200
	/**
1201
	 * Remove <US> character (unit separator) from exported strings
1202
	 *
1203
	 * @since 2.0.22
1204
	 *
1205
	 * @param string $str
1206
	 */
1207
	private static function remove_invalid_characters_from_xml( &$str ) {
1208
		// Remove <US> character
1209
		$str = str_replace( '\x1F', '', $str );
1210
	}
1211
1212
	public static function migrate_form_settings_to_actions( $form_options, $form_id, &$imported = array(), $switch = false ) {
1213
		// Get post type
1214
		$post_type = FrmFormActionsController::$action_post_type;
1215
1216
		// Set up imported index, if not set up yet
1217
		if ( ! isset( $imported['imported']['actions'] ) ) {
1218
			$imported['imported']['actions'] = 0;
1219
		}
1220
1221
		// Migrate post settings to action
1222
		self::migrate_post_settings_to_action( $form_options, $form_id, $post_type, $imported, $switch );
1223
1224
		// Migrate email settings to action
1225
		self::migrate_email_settings_to_action( $form_options, $form_id, $post_type, $imported, $switch );
1226
	}
1227
1228
	/**
1229
	 * Migrate post settings to form action
1230
	 *
1231
	 * @param string $post_type
1232
	 */
1233
	private static function migrate_post_settings_to_action( $form_options, $form_id, $post_type, &$imported, $switch ) {
1234
		if ( ! isset( $form_options['create_post'] ) || ! $form_options['create_post'] ) {
1235
			return;
1236
		}
1237
1238
		$new_action = array(
1239
			'post_type'    => $post_type,
1240
			'post_excerpt' => 'wppost',
1241
			'post_title'   => __( 'Create Posts', 'formidable' ),
1242
			'menu_order'   => $form_id,
1243
			'post_status'  => 'publish',
1244
			'post_content' => array(),
1245
			'post_name'    => $form_id . '_wppost_1',
1246
		);
1247
1248
		$post_settings = array(
1249
			'post_type',
1250
			'post_category',
1251
			'post_content',
1252
			'post_excerpt',
1253
			'post_title',
1254
			'post_name',
1255
			'post_date',
1256
			'post_status',
1257
			'post_custom_fields',
1258
			'post_password',
1259
		);
1260
1261
		foreach ( $post_settings as $post_setting ) {
1262
			if ( isset( $form_options[ $post_setting ] ) ) {
1263
				$new_action['post_content'][ $post_setting ] = $form_options[ $post_setting ];
1264
			}
1265
			unset( $post_setting );
1266
		}
1267
1268
		$new_action['event'] = array( 'create', 'update' );
1269
1270
		if ( $switch ) {
1271
			// Fields with string or int saved.
1272
			$basic_fields = array(
1273
				'post_title',
1274
				'post_content',
1275
				'post_excerpt',
1276
				'post_password',
1277
				'post_date',
1278
				'post_status',
1279
			);
1280
1281
			// Fields with arrays saved.
1282
			$array_fields = array( 'post_category', 'post_custom_fields' );
1283
1284
			$new_action['post_content'] = self::switch_action_field_ids( $new_action['post_content'], $basic_fields, $array_fields );
1285
		}
1286
		$new_action['post_content'] = json_encode( $new_action['post_content'] );
1287
1288
		$exists = get_posts(
1289
			array(
1290
				'name'        => $new_action['post_name'],
1291
				'post_type'   => $new_action['post_type'],
1292
				'post_status' => $new_action['post_status'],
1293
				'numberposts' => 1,
1294
			)
1295
		);
1296
1297
		if ( ! $exists ) {
1298
			// this isn't an email, but we need to use a class that will always be included
1299
			FrmDb::save_json_post( $new_action );
1300
			$imported['imported']['actions'] ++;
1301
		}
1302
	}
1303
1304
	/**
1305
	 * Switch old field IDs for new field IDs in emails and post
1306
	 *
1307
	 * @since 2.0
1308
	 *
1309
	 * @param array $post_content - check for old field IDs
1310
	 * @param array $basic_fields - fields with string or int saved
1311
	 * @param array $array_fields - fields with arrays saved
1312
	 *
1313
	 * @return string $post_content - new field IDs
1314
	 */
1315
	private static function switch_action_field_ids( $post_content, $basic_fields, $array_fields = array() ) {
1316
		global $frm_duplicate_ids;
1317
1318
		// If there aren't IDs that were switched, end now
1319
		if ( ! $frm_duplicate_ids ) {
1320
			return;
1321
		}
1322
1323
		// Get old IDs
1324
		$old = array_keys( $frm_duplicate_ids );
1325
1326
		// Get new IDs
1327
		$new = array_values( $frm_duplicate_ids );
1328
1329
		// Do a str_replace with each item to set the new IDs
1330
		foreach ( $post_content as $key => $setting ) {
1331
			if ( ! is_array( $setting ) && in_array( $key, $basic_fields ) ) {
1332
				// Replace old IDs with new IDs
1333
				$post_content[ $key ] = str_replace( $old, $new, $setting );
1334
			} elseif ( is_array( $setting ) && in_array( $key, $array_fields ) ) {
1335
				foreach ( $setting as $k => $val ) {
1336
					// Replace old IDs with new IDs
1337
					$post_content[ $key ][ $k ] = str_replace( $old, $new, $val );
1338
				}
1339
			}
1340
			unset( $key, $setting );
1341
		}
1342
1343
		return $post_content;
1344
	}
1345
1346
	private static function migrate_email_settings_to_action( $form_options, $form_id, $post_type, &$imported, $switch ) {
1347
		// No old notifications or autoresponders to carry over
1348
		if ( ! isset( $form_options['auto_responder'] ) && ! isset( $form_options['notification'] ) && ! isset( $form_options['email_to'] ) ) {
1349
			return;
1350
		}
1351
1352
		// Initialize notifications array
1353
		$notifications = array();
1354
1355
		// Migrate regular notifications
1356
		self::migrate_notifications_to_action( $form_options, $form_id, $notifications );
1357
1358
		// Migrate autoresponders
1359
		self::migrate_autoresponder_to_action( $form_options, $form_id, $notifications );
1360
1361
		if ( empty( $notifications ) ) {
1362
			return;
1363
		}
1364
1365
		foreach ( $notifications as $new_notification ) {
1366
			$new_notification['post_type']    = $post_type;
1367
			$new_notification['post_excerpt'] = 'email';
1368
			$new_notification['post_title']   = __( 'Email Notification', 'formidable' );
1369
			$new_notification['menu_order']   = $form_id;
1370
			$new_notification['post_status']  = 'publish';
1371
1372
			// Switch field IDs and keys, if needed
1373
			if ( $switch ) {
1374
1375
				// Switch field IDs in email conditional logic
1376
				self::switch_email_condition_field_ids( $new_notification['post_content'] );
1377
1378
				// Switch all other field IDs in email
1379
				$new_notification['post_content'] = FrmFieldsHelper::switch_field_ids( $new_notification['post_content'] );
1380
			}
1381
			$new_notification['post_content'] = FrmAppHelper::prepare_and_encode( $new_notification['post_content'] );
1382
1383
			$exists = get_posts(
1384
				array(
1385
					'name'        => $new_notification['post_name'],
1386
					'post_type'   => $new_notification['post_type'],
1387
					'post_status' => $new_notification['post_status'],
1388
					'numberposts' => 1,
1389
				)
1390
			);
1391
1392
			if ( empty( $exists ) ) {
1393
				FrmDb::save_json_post( $new_notification );
1394
				$imported['imported']['actions'] ++;
1395
			}
1396
			unset( $new_notification );
1397
		}
1398
1399
		self::remove_deprecated_notification_settings( $form_id, $form_options );
1400
	}
1401
1402
	/**
1403
	 * Remove deprecated notification settings after migration
1404
	 *
1405
	 * @since 2.05
1406
	 *
1407
	 * @param int|string $form_id
1408
	 * @param array $form_options
1409
	 */
1410
	private static function remove_deprecated_notification_settings( $form_id, $form_options ) {
1411
		$delete_settings = array( 'notification', 'autoresponder', 'email_to' );
1412
		foreach ( $delete_settings as $index ) {
1413
			if ( isset( $form_options[ $index ] ) ) {
1414
				unset( $form_options[ $index ] );
1415
			}
1416
		}
1417
		FrmForm::update( $form_id, array( 'options' => $form_options ) );
1418
	}
1419
1420
	private static function migrate_notifications_to_action( $form_options, $form_id, &$notifications ) {
1421
		if ( ! isset( $form_options['notification'] ) && isset( $form_options['email_to'] ) && ! empty( $form_options['email_to'] ) ) {
1422
			// add old settings into notification array
1423
			$form_options['notification'] = array( 0 => $form_options );
1424
		} elseif ( isset( $form_options['notification']['email_to'] ) ) {
1425
			// make sure it's in the correct format
1426
			$form_options['notification'] = array( 0 => $form_options['notification'] );
1427
		}
1428
1429
		if ( isset( $form_options['notification'] ) && is_array( $form_options['notification'] ) ) {
1430
			foreach ( $form_options['notification'] as $email_key => $notification ) {
1431
1432
				$atts = array(
1433
					'email_to'      => '',
1434
					'reply_to'      => '',
1435
					'reply_to_name' => '',
1436
					'event'         => '',
1437
					'form_id'       => $form_id,
1438
					'email_key'     => $email_key,
1439
				);
1440
1441
				// Format the email data
1442
				self::format_email_data( $atts, $notification );
1443
1444
				if ( isset( $notification['twilio'] ) && $notification['twilio'] ) {
1445
					do_action( 'frm_create_twilio_action', $atts, $notification );
1446
				}
1447
1448
				// Setup the new notification
1449
				$new_notification = array();
1450
				self::setup_new_notification( $new_notification, $notification, $atts );
1451
1452
				$notifications[] = $new_notification;
1453
			}
1454
		}
1455
	}
1456
1457
	private static function format_email_data( &$atts, $notification ) {
1458
		// Format email_to
1459
		self::format_email_to_data( $atts, $notification );
1460
1461
		// Format the reply to email and name
1462
		$reply_fields = array(
1463
			'reply_to'      => '',
1464
			'reply_to_name' => '',
1465
		);
1466
		foreach ( $reply_fields as $f => $val ) {
1467
			if ( isset( $notification[ $f ] ) ) {
1468
				$atts[ $f ] = $notification[ $f ];
1469
				if ( 'custom' == $notification[ $f ] ) {
1470
					$atts[ $f ] = $notification[ 'cust_' . $f ];
1471
				} elseif ( is_numeric( $atts[ $f ] ) && ! empty( $atts[ $f ] ) ) {
1472
					$atts[ $f ] = '[' . $atts[ $f ] . ']';
1473
				}
1474
			}
1475
			unset( $f, $val );
1476
		}
1477
1478
		// Format event
1479
		$atts['event'] = array( 'create' );
1480
		if ( isset( $notification['update_email'] ) && 1 == $notification['update_email'] ) {
1481
			$atts['event'][] = 'update';
1482
		} elseif ( isset( $notification['update_email'] ) && 2 == $notification['update_email'] ) {
1483
			$atts['event'] = array( 'update' );
1484
		}
1485
	}
1486
1487
	private static function format_email_to_data( &$atts, $notification ) {
1488
		if ( isset( $notification['email_to'] ) ) {
1489
			$atts['email_to'] = preg_split( '/ (,|;) /', $notification['email_to'] );
1490
		} else {
1491
			$atts['email_to'] = array();
1492
		}
1493
1494
		if ( isset( $notification['also_email_to'] ) ) {
1495
			$email_fields     = (array) $notification['also_email_to'];
1496
			$atts['email_to'] = array_merge( $email_fields, $atts['email_to'] );
1497
			unset( $email_fields );
1498
		}
1499
1500
		foreach ( $atts['email_to'] as $key => $email_field ) {
1501
1502
			if ( is_numeric( $email_field ) ) {
1503
				$atts['email_to'][ $key ] = '[' . $email_field . ']';
1504
			}
1505
1506
			if ( strpos( $email_field, '|' ) ) {
1507
				$email_opt = explode( '|', $email_field );
1508
				if ( isset( $email_opt[0] ) ) {
1509
					$atts['email_to'][ $key ] = '[' . $email_opt[0] . ' show=' . $email_opt[1] . ']';
1510
				}
1511
				unset( $email_opt );
1512
			}
1513
		}
1514
		$atts['email_to'] = implode( ', ', $atts['email_to'] );
1515
	}
1516
1517
	private static function setup_new_notification( &$new_notification, $notification, $atts ) {
1518
		// Set up new notification
1519
		$new_notification = array(
1520
			'post_content' => array(
1521
				'email_to' => $atts['email_to'],
1522
				'event'    => $atts['event'],
1523
			),
1524
			'post_name'    => $atts['form_id'] . '_email_' . $atts['email_key'],
1525
		);
1526
1527
		// Add more fields to the new notification
1528
		$add_fields = array( 'email_message', 'email_subject', 'plain_text', 'inc_user_info', 'conditions' );
1529
		foreach ( $add_fields as $add_field ) {
1530
			if ( isset( $notification[ $add_field ] ) ) {
1531
				$new_notification['post_content'][ $add_field ] = $notification[ $add_field ];
1532
			} elseif ( in_array( $add_field, array( 'plain_text', 'inc_user_info' ) ) ) {
1533
				$new_notification['post_content'][ $add_field ] = 0;
1534
			} else {
1535
				$new_notification['post_content'][ $add_field ] = '';
1536
			}
1537
			unset( $add_field );
1538
		}
1539
1540
		// Set reply to
1541
		$new_notification['post_content']['reply_to'] = $atts['reply_to'];
1542
1543
		// Set from
1544
		if ( ! empty( $atts['reply_to'] ) || ! empty( $atts['reply_to_name'] ) ) {
1545
			$new_notification['post_content']['from'] = ( empty( $atts['reply_to_name'] ) ? '[sitename]' : $atts['reply_to_name'] ) . ' <' . ( empty( $atts['reply_to'] ) ? '[admin_email]' : $atts['reply_to'] ) . '>';
1546
		}
1547
	}
1548
1549
	/**
1550
	 * Switch field IDs in pre-2.0 email conditional logic
1551
	 *
1552
	 * @param $post_content array, pass by reference
1553
	 */
1554
	private static function switch_email_condition_field_ids( &$post_content ) {
1555
		// Switch field IDs in conditional logic
1556
		if ( isset( $post_content['conditions'] ) && is_array( $post_content['conditions'] ) ) {
1557
			foreach ( $post_content['conditions'] as $email_key => $val ) {
1558
				if ( is_numeric( $email_key ) ) {
1559
					$post_content['conditions'][ $email_key ] = self::switch_action_field_ids( $val, array( 'hide_field' ) );
1560
				}
1561
				unset( $email_key, $val );
1562
			}
1563
		}
1564
	}
1565
1566
	private static function migrate_autoresponder_to_action( $form_options, $form_id, &$notifications ) {
1567
		if ( isset( $form_options['auto_responder'] ) && $form_options['auto_responder'] && isset( $form_options['ar_email_message'] ) && $form_options['ar_email_message'] ) {
1568
			// migrate autoresponder
1569
1570
			$email_field = isset( $form_options['ar_email_to'] ) ? $form_options['ar_email_to'] : 0;
1571
			if ( strpos( $email_field, '|' ) ) {
1572
				// data from entries field
1573
				$email_field = explode( '|', $email_field );
1574
				if ( isset( $email_field[1] ) ) {
1575
					$email_field = $email_field[1];
1576
				}
1577
			}
1578
			if ( is_numeric( $email_field ) && ! empty( $email_field ) ) {
1579
				$email_field = '[' . $email_field . ']';
1580
			}
1581
1582
			$notification      = $form_options;
1583
			$new_notification2 = array(
1584
				'post_content' => array(
1585
					'email_message' => $notification['ar_email_message'],
1586
					'email_subject' => isset( $notification['ar_email_subject'] ) ? $notification['ar_email_subject'] : '',
1587
					'email_to'      => $email_field,
1588
					'plain_text'    => isset( $notification['ar_plain_text'] ) ? $notification['ar_plain_text'] : 0,
1589
					'inc_user_info' => 0,
1590
				),
1591
				'post_name'    => $form_id . '_email_' . count( $notifications ),
1592
			);
1593
1594
			$reply_to      = isset( $notification['ar_reply_to'] ) ? $notification['ar_reply_to'] : '';
1595
			$reply_to_name = isset( $notification['ar_reply_to_name'] ) ? $notification['ar_reply_to_name'] : '';
1596
1597
			if ( ! empty( $reply_to ) ) {
1598
				$new_notification2['post_content']['reply_to'] = $reply_to;
1599
			}
1600
1601
			if ( ! empty( $reply_to ) || ! empty( $reply_to_name ) ) {
1602
				$new_notification2['post_content']['from'] = ( empty( $reply_to_name ) ? '[sitename]' : $reply_to_name ) . ' <' . ( empty( $reply_to ) ? '[admin_email]' : $reply_to ) . '>';
1603
			}
1604
1605
			$notifications[] = $new_notification2;
1606
			unset( $new_notification2 );
1607
		}
1608
	}
1609
}
1610
1611