PluginProbe
Formidable Forms – WordPress Form Builder for Contact Forms, Calculators, Quizzes & More / 5.5.2
Formidable Forms – WordPress Form Builder for Contact Forms, Calculators, Quizzes & More v5.5.2
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.2, at classes/controllers/FrmXMLController.php

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