Completed
Push — master ( 650818...b36e91 )
by Stephanie
18s queued 10s
created

FrmXMLHelper::maybe_libxml_disable_entity_loader()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

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

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
1675
	 *
1676
	 * @return boolean
1677
	 */
1678
	public static function maybe_libxml_disable_entity_loader( $loader ) {
1679
		if ( version_compare( phpversion(), '8.0', '<' ) && function_exists( 'libxml_disable_entity_loader' ) ) {
1680
			$loader = libxml_disable_entity_loader( $loader ); // phpcs:disable Generic.PHP.DeprecatedFunctions.Deprecated
1681
		}
1682
1683
		return $loader;
1684
	}
1685
1686
	public static function check_if_libxml_disable_entity_loader_exists() {
1687
		return version_compare( phpversion(), '8.0', '<' ) && ! function_exists( 'libxml_disable_entity_loader' );
1688
	}
1689
}
1690
1691