Completed
Push — master ( 49319d...867d5a )
by Stephanie
02:43 queued 10s
created

FrmXMLHelper::switch_action_field_ids()   B

Complexity

Conditions 8
Paths 5

Size

Total Lines 30

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 8
nc 5
nop 3
dl 0
loc 30
rs 8.1954
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
		if ( ! defined( 'WP_IMPORTING' ) ) {
63
			define( 'WP_IMPORTING', true );
64
		}
65
66
		$imported = self::pre_import_data();
67
68
		foreach ( array( 'term', 'form', 'view' ) as $item_type ) {
69
			// Grab cats, tags, and terms, or forms or posts.
70
			if ( isset( $xml->{$item_type} ) ) {
71
				$function_name = 'import_xml_' . $item_type . 's';
72
				$imported      = self::$function_name( $xml->{$item_type}, $imported );
73
				unset( $function_name, $xml->{$item_type} );
74
			}
75
		}
76
77
		$imported = apply_filters( 'frm_importing_xml', $imported, $xml );
78
79
		if ( ! isset( $imported['form_status'] ) || empty( $imported['form_status'] ) ) {
80
			// Check for an error message in the XML.
81
			if ( isset( $xml->Code ) && isset( $xml->Message ) ) { // phpcs:ignore WordPress.NamingConventions
82
				$imported['error'] = reset( $xml->Message ); // phpcs:ignore WordPress.NamingConventions
83
			}
84
		}
85
86
		return $imported;
87
	}
88
89
	/**
90
	 * @since 3.06
91
	 * @return array
92
	 */
93
	private static function pre_import_data() {
94
		$defaults = array(
95
			'forms'   => 0,
96
			'fields'  => 0,
97
			'terms'   => 0,
98
			'posts'   => 0,
99
			'views'   => 0,
100
			'actions' => 0,
101
			'styles'  => 0,
102
		);
103
104
		return array(
105
			'imported' => $defaults,
106
			'updated'  => $defaults,
107
			'forms'    => array(),
108
			'terms'    => array(),
109
		);
110
	}
111
112
	public static function import_xml_terms( $terms, $imported ) {
113
		foreach ( $terms as $t ) {
114
			if ( term_exists( (string) $t->term_slug, (string) $t->term_taxonomy ) ) {
115
				continue;
116
			}
117
118
			$parent = self::get_term_parent_id( $t );
119
120
			$term = wp_insert_term(
121
				(string) $t->term_name,
122
				(string) $t->term_taxonomy,
123
				array(
124
					'slug'        => (string) $t->term_slug,
125
					'description' => (string) $t->term_description,
126
					'parent'      => empty( $parent ) ? 0 : $parent,
127
				)
128
			);
129
130
			if ( $term && is_array( $term ) ) {
131
				$imported['imported']['terms'] ++;
132
				$imported['terms'][ (int) $t->term_id ] = $term['term_id'];
133
			}
134
135
			unset( $term, $t );
136
		}
137
138
		return $imported;
139
	}
140
141
	/**
142
	 * @since 2.0.8
143
	 */
144
	private static function get_term_parent_id( $t ) {
145
		$parent = (string) $t->term_parent;
146
		if ( ! empty( $parent ) ) {
147
			$parent = term_exists( (string) $t->term_parent, (string) $t->term_taxonomy );
148
			if ( $parent ) {
149
				$parent = $parent['term_id'];
150
			} else {
151
				$parent = 0;
152
			}
153
		}
154
155
		return $parent;
156
	}
157
158
	public static function import_xml_forms( $forms, $imported ) {
159
		$child_forms = array();
160
161
		// Import child forms first
162
		self::put_child_forms_first( $forms );
163
164
		foreach ( $forms as $item ) {
165
			$form = self::fill_form( $item );
166
167
			self::update_custom_style_setting_on_import( $form );
168
169
			$this_form = self::maybe_get_form( $form );
170
171
			$old_id      = false;
172
			$form_fields = false;
173
			if ( ! empty( $this_form ) ) {
174
				$form_id = $this_form->id;
175
				$old_id  = $this_form->id;
176
				self::update_form( $this_form, $form, $imported );
177
178
				$form_fields = self::get_form_fields( $form_id );
179
			} else {
180
				$form_id = FrmForm::create( $form );
181
				if ( $form_id ) {
182
					if ( empty( $form['parent_form_id'] ) ) {
183
						// Don't include the repeater form in the imported count.
184
						$imported['imported']['forms'] ++;
185
					}
186
187
					// Keep track of whether this specific form was updated or not.
188
					$imported['form_status'][ $form_id ] = 'imported';
189
					self::track_imported_child_forms( (int) $form_id, $form['parent_form_id'], $child_forms );
190
				}
191
			}
192
193
			self::import_xml_fields( $item->field, $form_id, $this_form, $form_fields, $imported );
194
195
			self::delete_removed_fields( $form_fields );
196
197
			// Update field ids/keys to new ones.
198
			do_action( 'frm_after_duplicate_form', $form_id, $form, array( 'old_id' => $old_id ) );
199
200
			$imported['forms'][ (int) $item->id ] = $form_id;
201
202
			// Send pre 2.0 form options through function that creates actions.
203
			self::migrate_form_settings_to_actions( $form['options'], $form_id, $imported, true );
204
205
			do_action( 'frm_after_import_form', $form_id, $form );
206
207
			unset( $form, $item );
208
		}
209
210
		self::maybe_update_child_form_parent_id( $imported['forms'], $child_forms );
211
212
		return $imported;
213
	}
214
215
	private static function fill_form( $item ) {
216
		$form = array(
217
			'id'             => (int) $item->id,
218
			'form_key'       => (string) $item->form_key,
219
			'name'           => (string) $item->name,
220
			'description'    => (string) $item->description,
221
			'options'        => (string) $item->options,
222
			'logged_in'      => (int) $item->logged_in,
223
			'is_template'    => (int) $item->is_template,
224
			'editable'       => (int) $item->editable,
225
			'status'         => (string) $item->status,
226
			'parent_form_id' => isset( $item->parent_form_id ) ? (int) $item->parent_form_id : 0,
227
			'created_at'     => gmdate( 'Y-m-d H:i:s', strtotime( (string) $item->created_at ) ),
228
		);
229
230
		if ( empty( $item->created_at ) ) {
231
			$form['created_at'] = current_time( 'mysql', 1 );
232
		}
233
234
		$form['options'] = FrmAppHelper::maybe_json_decode( $form['options'] );
235
236
		return $form;
237
	}
238
239
	private static function maybe_get_form( $form ) {
240
		// if template, allow to edit if form keys match, otherwise, creation date must also match
241
		$edit_query = array(
242
			'form_key'    => $form['form_key'],
243
			'is_template' => $form['is_template'],
244
		);
245
		if ( ! $form['is_template'] ) {
246
			$edit_query['created_at'] = $form['created_at'];
247
		}
248
249
		$edit_query = apply_filters( 'frm_match_xml_form', $edit_query, $form );
250
251
		return FrmForm::getAll( $edit_query, '', 1 );
252
	}
253
254
	private static function update_form( $this_form, $form, &$imported ) {
255
		$form_id = $this_form->id;
256
		FrmForm::update( $form_id, $form );
257
		if ( empty( $form['parent_form_id'] ) ) {
258
			// Don't include the repeater form in the updated count.
259
			$imported['updated']['forms'] ++;
260
		}
261
262
		// Keep track of whether this specific form was updated or not
263
		$imported['form_status'][ $form_id ] = 'updated';
264
	}
265
266
	private static function get_form_fields( $form_id ) {
267
		$form_fields = FrmField::get_all_for_form( $form_id, '', 'exclude', 'exclude' );
268
		$old_fields  = array();
269
		foreach ( $form_fields as $f ) {
270
			$old_fields[ $f->id ]        = $f;
271
			$old_fields[ $f->field_key ] = $f->id;
272
			unset( $f );
273
		}
274
		$form_fields = $old_fields;
275
276
		return $form_fields;
277
	}
278
279
	/**
280
	 * Delete any fields attached to this form that were not included in the template
281
	 */
282
	private static function delete_removed_fields( $form_fields ) {
283
		if ( ! empty( $form_fields ) ) {
284
			foreach ( $form_fields as $field ) {
285
				if ( is_object( $field ) ) {
286
					FrmField::destroy( $field->id );
287
				}
288
				unset( $field );
289
			}
290
		}
291
	}
292
293
	/**
294
	 * Put child forms first so they will be imported before parents
295
	 *
296
	 * @since 2.0.16
297
	 *
298
	 * @param array $forms
299
	 */
300
	private static function put_child_forms_first( &$forms ) {
301
		$child_forms   = array();
302
		$regular_forms = array();
303
304
		foreach ( $forms as $form ) {
305
			$parent_form_id = isset( $form->parent_form_id ) ? (int) $form->parent_form_id : 0;
306
307
			if ( $parent_form_id ) {
308
				$child_forms[] = $form;
309
			} else {
310
				$regular_forms[] = $form;
311
			}
312
		}
313
314
		$forms = array_merge( $child_forms, $regular_forms );
315
	}
316
317
	/**
318
	 * Keep track of all imported child forms
319
	 *
320
	 * @since 2.0.16
321
	 *
322
	 * @param int $form_id
323
	 * @param int $parent_form_id
324
	 * @param array $child_forms
325
	 */
326
	private static function track_imported_child_forms( $form_id, $parent_form_id, &$child_forms ) {
327
		if ( $parent_form_id ) {
328
			$child_forms[ $form_id ] = $parent_form_id;
329
		}
330
	}
331
332
	/**
333
	 * Update the parent_form_id on imported child forms
334
	 * Child forms are imported first so their parent_form_id will need to be updated after the parent is imported
335
	 *
336
	 * @since 2.0.6
337
	 *
338
	 * @param array $imported_forms
339
	 * @param array $child_forms
340
	 */
341
	private static function maybe_update_child_form_parent_id( $imported_forms, $child_forms ) {
342
		foreach ( $child_forms as $child_form_id => $old_parent_form_id ) {
343
344
			if ( isset( $imported_forms[ $old_parent_form_id ] ) && $imported_forms[ $old_parent_form_id ] != $old_parent_form_id ) {
345
				// Update all children with this old parent_form_id
346
				$new_parent_form_id = (int) $imported_forms[ $old_parent_form_id ];
347
348
				FrmForm::update( $child_form_id, array( 'parent_form_id' => $new_parent_form_id ) );
349
			}
350
		}
351
	}
352
353
	/**
354
	 * Import all fields for a form
355
	 *
356
	 * @since 2.0.13
357
	 *
358
	 * TODO: Cut down on params
359
	 */
360
	private static function import_xml_fields( $xml_fields, $form_id, $this_form, &$form_fields, &$imported ) {
361
		$in_section                = 0;
362
		$keys_by_original_field_id = array();
363
364
		foreach ( $xml_fields as $field ) {
365
			$f = self::fill_field( $field, $form_id );
366
367
			self::set_default_value( $f );
368
			self::maybe_add_required( $f );
369
			self::maybe_update_in_section_variable( $in_section, $f );
370
			self::maybe_update_form_select( $f, $imported );
371
			self::maybe_update_get_values_form_setting( $imported, $f );
372
			self::migrate_placeholders( $f );
373
374
			if ( ! empty( $this_form ) ) {
375
				// check for field to edit by field id
376
				if ( isset( $form_fields[ $f['id'] ] ) ) {
377
					FrmField::update( $f['id'], $f );
378
					$imported['updated']['fields'] ++;
379
380
					unset( $form_fields[ $f['id'] ] );
381
382
					//unset old field key
383
					if ( isset( $form_fields[ $f['field_key'] ] ) ) {
384
						unset( $form_fields[ $f['field_key'] ] );
385
					}
386
				} elseif ( isset( $form_fields[ $f['field_key'] ] ) ) {
387
					$keys_by_original_field_id[ $f['id'] ] = $f['field_key'];
388
389
					// check for field to edit by field key
390
					unset( $f['id'] );
391
392
					FrmField::update( $form_fields[ $f['field_key'] ], $f );
393
					$imported['updated']['fields'] ++;
394
395
					unset( $form_fields[ $form_fields[ $f['field_key'] ] ] ); //unset old field id
396
					unset( $form_fields[ $f['field_key'] ] ); //unset old field key
397
				} else {
398
					// if no matching field id or key in this form, create the field
399
					self::create_imported_field( $f, $imported );
400
				}
401
			} else {
402
403
				self::create_imported_field( $f, $imported );
404
			}
405
		}
406
407
		if ( $keys_by_original_field_id ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $keys_by_original_field_id of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

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