PluginProbe
Formidable Forms – WordPress Form Builder for Contact Forms, Calculators, Quizzes & More / 5.5.6
Formidable Forms – WordPress Form Builder for Contact Forms, Calculators, Quizzes & More v5.5.6
6.35 6.34 6.33.1 6.33 6.32.1 6.32 6.31 6.25 6.25.1 6.26 6.26.1 6.27 6.28 6.29 6.3 6.3.1 6.3.2 6.30 6.4 6.4.1 6.4.2 6.5 6.5.1 6.5.2 6.5.3 All 141 releases
formidable / classes / controllers / FrmXMLController.php

FrmXMLController.php in Formidable Forms – WordPress Form Builder for Contact Forms, Calculators, Quizzes & More 5.5.6, at classes/controllers/FrmXMLController.php

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