Completed
Pull Request — master (#200)
by Stephanie
02:17
created

FrmXMLController::allow_mime()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
nc 4
nop 1
dl 0
loc 13
rs 9.8333
c 0
b 0
f 0
1
<?php
2
3
class FrmXMLController {
4
5
	public static function menu() {
6
		add_submenu_page( 'formidable', 'Formidable | ' . __( 'Import/Export', 'formidable' ), __( 'Import/Export', 'formidable' ), 'frm_edit_forms', 'formidable-import', 'FrmXMLController::route' );
7
	}
8
9
	public static function add_default_templates() {
10
		if ( ! function_exists( 'libxml_disable_entity_loader' ) ) {
11
			// XML import is not enabled on your server
12
			return;
13
		}
14
15
		$set_err = libxml_use_internal_errors( true );
16
		$loader  = libxml_disable_entity_loader( true );
17
18
		$files = apply_filters( 'frm_default_templates_files', array() );
19
20
		foreach ( (array) $files as $file ) {
21
			FrmXMLHelper::import_xml( $file );
22
			unset( $file );
23
		}
24
25
		unset( $files );
26
27
		libxml_use_internal_errors( $set_err );
28
		libxml_disable_entity_loader( $loader );
29
	}
30
31
	/**
32
	 * Use the template link to install the XML template
33
	 *
34
	 * @since 3.06
35
	 */
36
	public static function install_template() {
37
		FrmAppHelper::permission_check( 'frm_edit_forms' );
38
		check_ajax_referer( 'frm_ajax', 'nonce' );
39
40
		$url = FrmAppHelper::get_param( 'xml', '', 'post', 'esc_url_raw' );
41
42
		$form = self::get_posted_form();
43
		self::override_url( $form, $url );
44
45
		$response = wp_remote_get( $url );
46
		$body     = wp_remote_retrieve_body( $response );
47
		$xml      = simplexml_load_string( $body );
48
49
		if ( ! $xml ) {
50
			$response = array(
51
				'message' => __( 'There was an error reading the form template', 'formidable' ),
52
			);
53
			echo wp_json_encode( $response );
54
			wp_die();
55
		}
56
57
		self::set_new_form_name( $xml );
58
59
		$imported = FrmXMLHelper::import_xml_now( $xml );
60
		if ( isset( $imported['form_status'] ) && ! empty( $imported['form_status'] ) ) {
61
			// Get the last form id in case there are child forms.
62
			end( $imported['form_status'] );
63
			$form_id  = key( $imported['form_status'] );
64
			$response = array(
65
				'id'       => $form_id,
66
				'redirect' => FrmForm::get_edit_link( $form_id ),
67
				'success'  => 1,
68
			);
69
			if ( ! empty( $imported['imported']['posts'] ) ) {
70
				// Return the link to the last page created.
71
				$pages = $imported['posts'];
72
			}
73
74
			if ( ! empty( $form ) ) {
75
				// Create selected pages with the correct shortcodes.
76
				$pages = self::create_pages_for_import( $form );
77
			}
78
79
			if ( isset( $pages ) && ! empty( $pages ) ) {
80
				$post_id = end( $pages );
81
				$response['redirect'] = get_permalink( $post_id );
82
			}
83
		} else {
84
			if ( isset( $imported['error'] ) ) {
85
				$message = $imported['error'];
86
			} else {
87
				$message = __( 'There was an error importing form', 'formidable' );
88
			}
89
			$response = array(
90
				'message' => $message,
91
			);
92
93
		}
94
95
		$response = apply_filters( 'frm_xml_response', $response, compact( 'form', 'imported' ) );
96
97
		echo wp_json_encode( $response );
98
		wp_die();
99
	}
100
101
	/**
102
	 * @since 4.06.02
103
	 */
104
	private static function get_posted_form() {
105
		$form = FrmAppHelper::get_param( 'form', '', 'post', 'wp_unslash' );
106
		if ( empty( $form ) ) {
107
			return $form;
108
		}
109
		$form = json_decode( $form, true );
110
		return $form;
111
	}
112
113
	/**
114
	 * Get a different URL depending on the selection in the form.
115
	 *
116
	 * @since 4.06.02
117
	 */
118
	private static function override_url( $form, &$url ) {
119
		$selected_form = self::get_selected_in_form( $form, 'form' );
120
		if ( empty( $selected_form ) ) {
121
			return;
122
		}
123
124
		$selected_xml  = isset( $form['xml'] ) && isset( $form['xml'][ $selected_form ] ) ? $form['xml'][ $selected_form ] : '';
125
		if ( empty( $selected_xml ) || strpos( $selected_xml, 'http' ) !== 0 ) {
126
			return;
127
		}
128
129
		$url = $selected_xml;
130
	}
131
132
	/**
133
	 * @since 4.06.02
134
	 */
135
	private static function get_selected_in_form( $form, $value = 'form' ) {
136
		if ( ! empty( $form ) && isset( $form[ $value ] ) && ! empty( $form[ $value ] ) ) {
137
			return $form[ $value ];
138
		}
139
140
		return '';
141
	}
142
143
	/**
144
	 * @since 4.06.02
145
	 *
146
	 * @param array $form The posted form values.
147
	 *
148
	 * @return array The array of created pages.
149
	 */
150
	private static function create_pages_for_import( $form ) {
151
		if ( ! isset( $form['pages'] ) || empty( $form['pages'] ) ) {
152
			return;
153
		}
154
155
		$form_key = self::get_selected_in_form( $form, 'form' );
156
		$view_keys = self::get_selected_in_form( $form, 'view' );
157
158
		$page_ids = array();
159
		foreach ( (array) $form['pages'] as $for => $name ) {
160
			if ( empty( $name ) ) {
161
				// Don't create a page if no title is given.
162
				continue;
163
			}
164
165
			if ( $for === 'view' ) {
166
				$item_key  = is_array( $view_keys ) ? $view_keys[ $form_key ] : $view_keys;
167
				$shortcode = '[display-frm-data id=%1$s filter=limited]';
168
			} elseif ( $for === 'form' ) {
169
				$item_key = $form_key;
170
				$shortcode = '[formidable id=%1$s]';
171
			} else {
172
				$item_key  = self::get_selected_in_form( $form, 'form' );
173
				$shortcode = '[' . esc_html( $for ) . ' id=%1$s]';
174
			}
175
176
			if ( empty( $item_key ) ) {
177
				// Don't create it if the shortcode won't show anything.
178
				continue;
179
			}
180
181
			$page_ids[ $for ] = wp_insert_post(
182
				array(
183
					'post_title'   => $name,
184
					'post_type'    => 'page',
185
					'post_content' => sprintf( $shortcode, $item_key ),
186
				)
187
			);
188
		}
189
190
		return $page_ids;
191
	}
192
193
	/**
194
	 * Change the name of the last form that is not a child.
195
	 * This will allow for lookup fields and embedded forms
196
	 * since we redirect to the last form.
197
	 *
198
	 * @since 3.06
199
	 *
200
	 * @param object $xml The values included in the XML.
201
	 */
202
	private static function set_new_form_name( &$xml ) {
203
		if ( ! isset( $xml->form ) ) {
204
			return;
205
		}
206
207
		$name        = FrmAppHelper::get_param( 'name', '', 'post', 'sanitize_text_field' );
208
		$description = FrmAppHelper::get_param( 'desc', '', 'post', 'sanitize_textarea_field' );
209
		if ( empty( $name ) && empty( $description ) ) {
210
			return;
211
		}
212
213
		// Get the main form ID.
214
		$set_name = 0;
215
		foreach ( $xml->form as $form ) {
216
			if ( ! isset( $form->parent_form_id ) || empty( $form->parent_form_id ) ) {
217
				$set_name = $form->id;
218
			}
219
		}
220
221
		foreach ( $xml->form as $form ) {
222
			// Maybe set the form name if this isn't a child form.
223
			if ( $set_name == $form->id ) {
224
				$form->name        = $name;
225
				$form->description = $description;
226
			}
227
228
			// Use a unique key to prevent editing existing form.
229
			$name           = sanitize_title( $form->name );
230
			$form->form_key = FrmAppHelper::get_unique_key( $name, 'frm_forms', 'form_key' );
231
		}
232
	}
233
234
	public static function route() {
235
		$action = isset( $_REQUEST['frm_action'] ) ? 'frm_action' : 'action';
236
		$action = FrmAppHelper::get_param( $action, '', 'get', 'sanitize_title' );
237
		FrmAppHelper::include_svg();
238
239
		if ( 'import_xml' === $action ) {
240
			return self::import_xml();
241
		} elseif ( 'export_xml' === $action ) {
242
			return self::export_xml();
243
		} elseif ( apply_filters( 'frm_xml_route', true, $action ) ) {
244
			return self::form();
245
		}
246
	}
247
248
	public static function form( $errors = array(), $message = '' ) {
249
		$where = array(
250
			'status' => array( null, '', 'published' ),
251
		);
252
		$forms = FrmForm::getAll( $where, 'name' );
253
254
		$export_types = array(
255
			'forms' => __( 'Forms', 'formidable' ),
256
			'items' => __( 'Entries', 'formidable' ),
257
		);
258
		$export_types = apply_filters( 'frm_xml_export_types', $export_types );
259
260
		$export_format = array(
261
			'xml' => array(
262
				'name'    => 'XML',
263
				'support' => 'forms',
264
				'count'   => 'multiple',
265
			),
266
			'csv' => array(
267
				'name'    => 'CSV',
268
				'support' => 'items',
269
				'count'   => 'single',
270
			),
271
		);
272
		$export_format = apply_filters( 'frm_export_formats', $export_format );
273
274
		include( FrmAppHelper::plugin_path() . '/classes/views/xml/import_form.php' );
275
	}
276
277
	public static function import_xml() {
278
		$errors  = array();
279
		$message = '';
280
281
		$permission_error = FrmAppHelper::permission_nonce_error( 'frm_edit_forms', 'import-xml', 'import-xml-nonce' );
282
		if ( false !== $permission_error ) {
283
			$errors[] = $permission_error;
284
			self::form( $errors );
285
286
			return;
287
		}
288
289
		$has_file = isset( $_FILES ) && isset( $_FILES['frm_import_file'] ) && ! empty( $_FILES['frm_import_file']['name'] ) && ! empty( $_FILES['frm_import_file']['size'] ) && (int) $_FILES['frm_import_file']['size'] > 0;
290
		if ( ! $has_file ) {
291
			$errors[] = __( 'Oops, you didn\'t select a file.', 'formidable' );
292
			self::form( $errors );
293
294
			return;
295
		}
296
297
		// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash
298
		$file = isset( $_FILES['frm_import_file']['tmp_name'] ) ? sanitize_option( 'upload_path', $_FILES['frm_import_file']['tmp_name'] ) : '';
299
300
		if ( ! is_uploaded_file( $file ) ) {
301
			unset( $file );
302
			$errors[] = __( 'The file does not exist, please try again.', 'formidable' );
303
			self::form( $errors );
304
305
			return;
306
		}
307
308
		//add_filter('upload_mimes', 'FrmXMLController::allow_mime');
309
310
		$export_format = array(
311
			'xml' => array(
312
				'name'    => 'XML',
313
				'support' => 'forms',
314
				'count'   => 'multiple',
315
			),
316
		);
317
		$export_format = apply_filters( 'frm_export_formats', $export_format );
318
319
		// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash
320
		$file_type = sanitize_option( 'upload_path', $_FILES['frm_import_file']['name'] );
321
		$file_type = strtolower( pathinfo( $file_type, PATHINFO_EXTENSION ) );
322
		if ( 'xml' !== $file_type && isset( $export_format[ $file_type ] ) ) {
323
			// allow other file types to be imported
324
			do_action( 'frm_before_import_' . $file_type );
325
326
			return;
327
		}
328
		unset( $file_type );
329
330
		if ( ! function_exists( 'libxml_disable_entity_loader' ) ) {
331
			$errors[] = __( 'XML import is not enabled on your server with the libxml_disable_entity_loader function.', 'formidable' );
332
			self::form( $errors );
333
334
			return;
335
		}
336
337
		$set_err = libxml_use_internal_errors( true );
338
		$loader  = libxml_disable_entity_loader( true );
339
340
		$result = FrmXMLHelper::import_xml( $file );
341
		FrmXMLHelper::parse_message( $result, $message, $errors );
342
343
		unset( $file );
344
345
		libxml_use_internal_errors( $set_err );
346
		libxml_disable_entity_loader( $loader );
347
348
		self::form( $errors, $message );
349
	}
350
351
	public static function export_xml() {
352
		$error = FrmAppHelper::permission_nonce_error( 'frm_edit_forms', 'export-xml', 'export-xml-nonce' );
353
		if ( ! empty( $error ) ) {
354
			wp_die( esc_html( $error ) );
355
		}
356
357
		$ids    = FrmAppHelper::get_post_param( 'frm_export_forms', array(), 'sanitize_text_field' );
358
		$type   = FrmAppHelper::get_post_param( 'type', array(), 'sanitize_text_field' );
359
		$format = FrmAppHelper::get_post_param( 'format', 'xml', 'sanitize_title' );
360
361
		if ( ! headers_sent() && ! $type ) {
362
			wp_redirect( esc_url_raw( admin_url( 'admin.php?page=formidable-import' ) ) );
363
			die();
364
		}
365
366
		if ( 'xml' === $format ) {
367
			self::generate_xml( $type, compact( 'ids' ) );
368
		} elseif ( 'csv' === $format ) {
369
			self::generate_csv( compact( 'ids' ) );
370
		} else {
371
			do_action( 'frm_export_format_' . $format, compact( 'ids' ) );
372
		}
373
374
		wp_die();
375
	}
376
377
	public static function generate_xml( $type, $args = array() ) {
378
		global $wpdb;
379
380
		self::prepare_types_array( $type );
381
382
		$tables = array(
383
			'items'   => $wpdb->prefix . 'frm_items',
384
			'forms'   => $wpdb->prefix . 'frm_forms',
385
			'posts'   => $wpdb->posts,
386
			'styles'  => $wpdb->posts,
387
			'actions' => $wpdb->posts,
388
		);
389
390
		$defaults = array(
391
			'ids' => false,
392
		);
393
		$args     = wp_parse_args( $args, $defaults );
394
395
		// Make sure ids are numeric.
396
		if ( is_array( $args['ids'] ) && ! empty( $args['ids'] ) ) {
397
			$args['ids'] = array_filter( $args['ids'], 'is_numeric' );
398
		}
399
400
		$records = array();
401
402
		foreach ( $type as $tb_type ) {
403
			$where = array();
404
			$join  = '';
405
			$table = $tables[ $tb_type ];
406
407
			$select     = $table . '.id';
408
			$query_vars = array();
409
410
			switch ( $tb_type ) {
411
				case 'forms':
412
					//add forms
413
					if ( $args['ids'] ) {
414
						$where[] = array(
415
							'or'                       => 1,
416
							$table . '.id'             => $args['ids'],
417
							$table . '.parent_form_id' => $args['ids'],
418
						);
419
					} else {
420
						$where[ $table . '.status !' ] = 'draft';
421
					}
422
					break;
423
				case 'actions':
424
					$select             = $table . '.ID';
425
					$where['post_type'] = FrmFormActionsController::$action_post_type;
426
					if ( ! empty( $args['ids'] ) ) {
427
						$where['menu_order'] = $args['ids'];
428
					}
429
					break;
430
				case 'items':
431
					// $join = "INNER JOIN {$wpdb->prefix}frm_item_metas im ON ($table.id = im.item_id)";
432
					if ( $args['ids'] ) {
433
						$where[ $table . '.form_id' ] = $args['ids'];
434
					}
435
					break;
436
				case 'styles':
437
					// Loop through all exported forms and get their selected style IDs.
438
					$frm_style     = new FrmStyle();
439
					$default_style = $frm_style->get_default_style();
440
					$form_ids      = $args['ids'];
441
					$style_ids     = array();
442
					foreach ( $form_ids as $form_id ) {
443
						$form_data = FrmForm::getOne( $form_id );
444
						// For forms that have not been updated while running 2.0, check if custom_style is set.
445
						if ( isset( $form_data->options['custom_style'] ) ) {
446
							if ( 1 === absint( $form_data->options['custom_style'] ) ) {
447
								$style_ids[] = $default_style->ID;
448
							} else {
449
								$style_ids[] = $form_data->options['custom_style'];
450
							}
451
						}
452
						unset( $form_id, $form_data );
453
					}
454
					$select             = $table . '.ID';
455
					$where['post_type'] = 'frm_styles';
456
457
					// Only export selected styles.
458
					if ( ! empty( $style_ids ) ) {
459
						$where['ID'] = $style_ids;
460
					}
461
					break;
462
				default:
463
					$select               = $table . '.ID';
464
					$join                 = ' INNER JOIN ' . $wpdb->postmeta . ' pm ON (pm.post_id=' . $table . '.ID)';
465
					$where['pm.meta_key'] = 'frm_form_id';
466
467
					if ( empty( $args['ids'] ) ) {
468
						$where['pm.meta_value >'] = 1;
469
					} else {
470
						$where['pm.meta_value'] = $args['ids'];
471
					}
472
			}
473
474
			$records[ $tb_type ] = FrmDb::get_col( $table . $join, $where, $select );
475
			unset( $tb_type );
476
		}
477
478
		$filename = self::get_file_name( $args, $type, $records );
479
480
		header( 'Content-Description: File Transfer' );
481
		header( 'Content-Disposition: attachment; filename=' . $filename );
482
		header( 'Content-Type: text/xml; charset=' . get_option( 'blog_charset' ), true );
483
484
		echo '<?xml version="1.0" encoding="' . esc_attr( get_bloginfo( 'charset' ) ) . "\" ?>\n";
485
		include( FrmAppHelper::plugin_path() . '/classes/views/xml/xml.php' );
486
	}
487
488
	private static function prepare_types_array( &$type ) {
489
		$type = (array) $type;
490
		if ( ! in_array( 'forms', $type ) && ( in_array( 'items', $type ) || in_array( 'posts', $type ) ) ) {
491
			// make sure the form is included if there are entries
492
			$type[] = 'forms';
493
		}
494
495
		if ( in_array( 'forms', $type ) ) {
496
			// include actions with forms
497
			$type[] = 'actions';
498
		}
499
	}
500
501
	/**
502
	 * Use a generic file name if multiple items are exported.
503
	 * Use the nme of the form if only one form is exported.
504
	 *
505
	 * @since 3.06
506
	 *
507
	 * @return string
508
	 */
509
	private static function get_file_name( $args, $type, $records ) {
510
		$has_one_form = isset( $records['forms'] ) && ! empty( $records['forms'] ) && count( $args['ids'] ) === 1;
511
		if ( $has_one_form ) {
512
			// one form is being exported
513
			$selected_form_id = reset( $args['ids'] );
514
			$filename         = 'form-' . $selected_form_id . '.xml';
515
516
			foreach ( $records['forms'] as $form_id ) {
517
				$filename = 'form-' . $form_id . '.xml';
518
				if ( $selected_form_id === $form_id ) {
519
					$form     = FrmForm::getOne( $form_id );
520
					$filename = sanitize_title( $form->name ) . '-form.xml';
521
					break;
522
				}
523
			}
524
		} else {
525
			$sitename = sanitize_key( get_bloginfo( 'name' ) );
526
527
			if ( ! empty( $sitename ) ) {
528
				$sitename .= '.';
529
			}
530
			$filename = $sitename . 'formidable.' . gmdate( 'Y-m-d' ) . '.xml';
531
		}
532
533
		return $filename;
534
	}
535
536
	public static function generate_csv( $atts ) {
537
		$form_ids = $atts['ids'];
538
		if ( empty( $form_ids ) ) {
539
			wp_die( esc_html__( 'Please select a form', 'formidable' ) );
540
		}
541
		self::csv( reset( $form_ids ) );
542
	}
543
544
	/**
545
	 * Export to CSV
546
	 *
547
	 * @since 2.0.19
548
	 */
549
	public static function csv( $form_id = false, $search = '', $fid = '' ) {
550
		FrmAppHelper::permission_check( 'frm_view_entries' );
551
552
		if ( ! $form_id ) {
553
			$form_id = FrmAppHelper::get_param( 'form', '', 'get', 'sanitize_text_field' );
554
			$search  = FrmAppHelper::get_param( ( isset( $_REQUEST['s'] ) ? 's' : 'search' ), '', 'get', 'sanitize_text_field' );
555
			$fid     = FrmAppHelper::get_param( 'fid', '', 'get', 'sanitize_text_field' );
556
		}
557
558
		set_time_limit( 0 ); //Remove time limit to execute this function
559
		$mem_limit = str_replace( 'M', '', ini_get( 'memory_limit' ) );
560
		if ( (int) $mem_limit < 256 ) {
561
			wp_raise_memory_limit();
562
		}
563
564
		global $wpdb;
565
566
		$form    = FrmForm::getOne( $form_id );
567
		$form_id = $form->id;
568
569
		$form_cols = self::get_fields_for_csv_export( $form_id, $form );
570
571
		$item_id = FrmAppHelper::get_param( 'item_id', 0, 'get', 'sanitize_text_field' );
572
		if ( ! empty( $item_id ) ) {
573
			$item_id = explode( ',', $item_id );
574
		}
575
576
		$query = array(
577
			'form_id' => $form_id,
578
		);
579
580
		if ( $item_id ) {
581
			$query['id'] = $item_id;
582
		}
583
584
		/**
585
		 * Allows the query to be changed for fetching the entry ids to include in the export
586
		 *
587
		 * $query is the array of options to be filtered. It includes form_id, and maybe id (array of entry ids),
588
		 * and the search query. This should return an array, but it can be handled as a string as well.
589
		 */
590
		$query = apply_filters( 'frm_csv_where', $query, compact( 'form_id', 'search', 'fid', 'item_id' ) );
591
592
		$entry_ids = FrmDb::get_col( $wpdb->prefix . 'frm_items it', $query );
593
		unset( $query );
594
595
		if ( empty( $entry_ids ) ) {
596
			esc_html_e( 'There are no entries for that form.', 'formidable' );
597
		} else {
598
			FrmCSVExportHelper::generate_csv( compact( 'form', 'entry_ids', 'form_cols' ) );
599
		}
600
601
		wp_die();
602
	}
603
604
	/**
605
	 * Get the fields that should be included in the CSV export
606
	 *
607
	 * @since 2.0.19
608
	 *
609
	 * @param int $form_id
610
	 * @param object $form
611
	 *
612
	 * @return array $csv_fields
613
	 */
614
	private static function get_fields_for_csv_export( $form_id, $form ) {
615
		$csv_fields       = FrmField::get_all_for_form( $form_id, '', 'include', 'include' );
616
		$no_export_fields = FrmField::no_save_fields();
617
		foreach ( $csv_fields as $k => $f ) {
618
			if ( in_array( $f->type, $no_export_fields ) ) {
619
				unset( $csv_fields[ $k ] );
620
			}
621
		}
622
623
		return $csv_fields;
624
	}
625
626
	public static function allow_mime( $mimes ) {
627
		if ( ! isset( $mimes['csv'] ) ) {
628
			// allow csv files
629
			$mimes['csv'] = 'text/csv';
630
		}
631
632
		if ( ! isset( $mimes['xml'] ) ) {
633
			// allow xml
634
			$mimes['xml'] = 'text/xml';
635
		}
636
637
		return $mimes;
638
	}
639
}
640