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

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