PluginProbe
Formidable Forms – WordPress Form Builder for Contact Forms, Calculators, Quizzes & More / 6.32.1
Formidable Forms – WordPress Form Builder for Contact Forms, Calculators, Quizzes & More v6.32.1
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 / models / FrmFormMigrator.php

FrmFormMigrator.php in Formidable Forms – WordPress Form Builder for Contact Forms, Calculators, Quizzes & More 6.32.1, at classes/models/FrmFormMigrator.php

743 lines 17.1 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 abstract class FrmFormMigrator {
7
8 /**
9 * @var bool
10 */
11 public $source_active;
12
13 /**
14 * @var string
15 */
16 public $slug;
17
18 /**
19 * @var string
20 */
21 public $path;
22
23 /**
24 * @var string
25 */
26 public $name;
27
28 /**
29 * @var array
30 */
31 public $response = array();
32
33 /**
34 * @var string
35 */
36 public $tracking = 'frm_forms_imported';
37
38 /**
39 * @var array
40 */
41 protected $fields_map = array();
42
43 /**
44 * @var mixed
45 */
46 protected $current_source_form;
47
48 /**
49 * @var array
50 */
51 protected $current_section = array();
52
53 /**
54 * Define required properties.
55 */
56 public function __construct() {
57 if ( ! is_admin() ) {
58 return;
59 }
60
61 if ( ! function_exists( 'is_plugin_active' ) ) {
62 require_once ABSPATH . '/wp-admin/includes/plugin.php';
63 }
64
65 $this->source_active = is_plugin_active( $this->path );
66
67 if ( ! $this->source_active ) {
68 // If source plugin is not installed, do nothing
69 return;
70 }
71
72 $this->maybe_add_to_import_page();
73
74 $this->response = array(
75 'upgrade_omit' => array(),
76 'unsupported' => array(),
77 );
78 }
79
80 /**
81 * @return void
82 */
83 private function maybe_add_to_import_page() {
84 add_action( 'frm_import_settings', array( $this, 'import_page' ) );
85 add_action( 'wp_ajax_frm_import_' . $this->slug, array( $this, 'import_forms' ) );
86 }
87
88 /**
89 * @return void
90 */
91 public function import_page() {
92 $forms = $this->get_forms();
93 // phpcs:disable Generic.WhiteSpace.ScopeIndent
94 ?>
95 <div class="wrap">
96 <h2 class="frm-h2"><?php echo esc_html( $this->name ); ?> Importer</h2>
97 <p class="howto">Import forms and settings automatically from <?php echo esc_html( $this->name ); ?>.</p>
98 <div id="welcome-panel">
99 <div style="margin-bottom:10px;">
100 <p class="about-description">
101 Select the forms to import.
102 </p>
103 <form class="frm_form_importer" method="post"
104 action="<?php echo esc_url( admin_url( 'admin-ajax.php' ) ); ?>">
105 <?php wp_nonce_field( 'nonce', 'frm_ajax' ); ?>
106 <input type="hidden" name="slug" value="<?php echo esc_attr( $this->slug ); ?>" />
107 <input type="hidden" name="action" value="frm_import_<?php echo esc_attr( $this->slug ); ?>" />
108 <div style="max-width:400px;text-align:left;">
109 <?php
110 if ( ! $forms ) {
111 esc_html_e( 'No Forms Found.', 'formidable' );
112 }
113 ?>
114 <?php foreach ( $forms as $form_id => $name ) { ?>
115 <p>
116 <label>
117 <input type="checkbox" name="form_id[]"
118 value="<?php echo esc_attr( $form_id ); ?>" checked="checked" />
119 <?php
120 echo esc_html( $name );
121 $new_form_id = $this->is_imported( $form_id );
122 ?>
123 <?php if ( $new_form_id ) { ?>
124 (<a href="<?php echo esc_url( FrmForm::get_edit_link( $new_form_id ) ); ?>">previously imported</a>)
125 <?php } ?>
126 </label>
127 </p>
128 <?php } ?>
129 </div>
130 <?php
131 $button_atts = array(
132 'type' => 'submit',
133 'class' => 'button button-primary frm-button-primary',
134 );
135
136 if ( ! $forms ) {
137 $button_atts['disabled'] = 'disabled';
138 }
139 ?>
140 <p class="submit">
141 <button <?php FrmAppHelper::array_to_html_params( $button_atts, true ); ?>>
142 <?php esc_html_e( 'Start Import', 'formidable' ); ?>
143 </button>
144 </p>
145 </form>
146 <div id="frm-importer-process" class="frm-importer-process frm_hidden">
147
148 <p class="process-count">
149 <span class="frm-wait" aria-hidden="true"></span>
150 Importing <span class="form-current">1</span> of <span class="form-total">0</span> forms
151 from <?php echo esc_html( $this->name ); ?>.
152 </p>
153
154 <p class="process-completed" class="frm_hidden">
155 <?php
156 printf(
157 // translators: %s is the number of forms that were imported.
158 esc_html__( 'The import process has finished! We have successfully imported %s forms. You can review the results below.', 'formidable' ),
159 '<span class="forms-completed"></span>'
160 );
161 ?>
162 </p>
163
164 <div class="status"></div>
165
166 </div>
167 </div>
168 </div>
169 </div>
170 <?php
171 // phpcs:enable Generic.WhiteSpace.ScopeIndent
172 }
173
174 /**
175 * Import all forms using ajax
176 *
177 * @return void
178 */
179 public function import_forms() {
180 check_ajax_referer( 'frm_ajax', 'nonce' );
181 FrmAppHelper::permission_check( 'frm_edit_forms' );
182
183 $forms = FrmAppHelper::get_simple_request(
184 array(
185 'param' => 'form_id',
186 'type' => 'post',
187 'sanitize' => 'absint',
188 )
189 );
190
191 if ( is_array( $forms ) ) {
192 $imported = array();
193
194 foreach ( $forms as $form_id ) {
195 $imported[] = $this->import_form( $form_id );
196 }
197 } else {
198 $imported = $this->import_form( $forms );
199 }
200
201 wp_send_json_success( $imported );
202 }
203
204 /**
205 * Import a single form
206 *
207 * @param int $source_id Source form ID.
208 *
209 * @return array
210 */
211 protected function import_form( $source_id ) {
212 $source_form = $this->get_form( $source_id );
213 $source_form_name = $this->get_form_name( $source_form );
214 $source_fields = $this->get_form_fields( $source_form );
215 $this->maybe_add_end_fields( $source_fields );
216
217 $this->current_source_form = $source_form;
218
219 // If form does not contain fields, bail.
220 if ( ! $source_fields ) {
221 wp_send_json_success(
222 array(
223 'error' => true,
224 'name' => esc_html( $source_form_name ),
225 'msg' => __( 'No form fields found.', 'formidable' ),
226 )
227 );
228 }
229
230 $form = $this->prepare_new_form( $source_id, $source_form_name );
231
232 $this->prepare_fields( $source_fields, $form );
233
234 $this->prepare_form( $source_form, $form );
235
236 $response = $this->add_form( $form );
237
238 // reset
239 $this->current_source_form = null;
240
241 return $response;
242 }
243
244 /**
245 * @param int|string $source_id
246 * @param string $source_form_name
247 *
248 * @return array
249 */
250 protected function prepare_new_form( $source_id, $source_form_name ) {
251 return array(
252 'import_form_id' => $source_id,
253 'fields' => array(),
254 'name' => $source_form_name,
255 'description' => '',
256 'options' => array(),
257 'actions' => array(),
258 );
259 }
260
261 /**
262 * @param array|object $form
263 * @param array $new_form
264 *
265 * @return void
266 */
267 protected function prepare_form( $form, &$new_form ) {
268 // Customize this function
269 }
270
271 /**
272 * @param array $fields
273 * @param array $form
274 *
275 * @return void
276 */
277 protected function prepare_fields( $fields, &$form ) {
278 $field_order = 1;
279
280 foreach ( $fields as $field ) {
281 $field = (array) $field;
282 $label = $this->get_field_label( $field );
283 $type = $this->get_field_type( $field );
284
285 // Check if field is unsupported. If unsupported make note and continue
286 if ( $this->is_unsupported_field( $type ) ) {
287 $this->response['unsupported'][] = $label;
288 continue;
289 }
290
291 if ( $this->should_skip_field( $type ) ) {
292 $this->response['upgrade_omit'][] = $label;
293 continue;
294 }
295
296 $new_type = $this->convert_field_type( $type, $field );
297 $new_field = FrmFieldsHelper::setup_new_vars( $new_type );
298 $new_field['name'] = $label;
299 $new_field['field_order'] = $field_order;
300 $new_field['original'] = $type;
301
302 $this->prepare_field( $field, $new_field );
303
304 $in_section = $this->current_section && ! in_array( $new_type, $this->fields_with_end(), true ) && $new_type !== 'break';
305
306 if ( $in_section ) {
307 $new_field['field_options']['in_section'] = $this->current_section['id'];
308 }
309
310 $form['fields'][] = $new_field;
311
312 if ( in_array( $new_type, $this->fields_with_end(), true ) ) {
313 $this->current_section = $field;
314 } elseif ( $new_type === 'break' || $new_type === 'end_divider' ) {
315 $this->current_section = array();
316 }
317
318 // This may occasionally skip one level/order e.g. after adding a
319 // List field, as field_order would already be prepared to be used.
320 ++$field_order;
321
322 if ( empty( $new_field['fields'] ) || ! is_array( $new_field['fields'] ) ) {
323 continue;
324 }
325
326 // we have (inner) fields to merge
327
328 $form['fields'] = array_merge( $form['fields'], $new_field['fields'] );
329 // Set the new field_order as it would have changed
330 $field_order = $new_field['current_order'];
331 }//end foreach
332 }
333
334 /**
335 * @param array|object $field
336 * @param array $new_field
337 *
338 * @return void
339 */
340 protected function prepare_field( $field, &$new_field ) {
341 // Customize this function
342 }
343
344 /**
345 * Add any field types that will need an end section field.
346 *
347 * @since 4.04.03
348 *
349 * @return array
350 */
351 protected function fields_with_end() {
352 return array( 'divider' );
353 }
354
355 /**
356 * @since 4.04.03
357 *
358 * @param array $fields
359 *
360 * @return void
361 */
362 protected function maybe_add_end_fields( &$fields ) {
363 $with_end = $this->fields_with_end();
364
365 if ( ! $with_end ) {
366 return;
367 }
368
369 $open = array();
370 $order = 0;
371
372 foreach ( $fields as $field ) {
373 ++$order;
374 $type = $this->get_field_type( $field );
375 $new_type = $this->convert_field_type( $type, $field );
376
377 if ( ! in_array( $new_type, $with_end, true ) && $new_type !== 'break' ) {
378 continue;
379 }
380
381 if ( $open ) {
382 $this->insert_end_section( $fields, $order );
383 $open = array();
384 }
385
386 if ( in_array( $new_type, $with_end, true ) ) {
387 $open = $field;
388 }
389 }
390
391 if ( $open ) {
392 $this->insert_end_section( $fields, $order );
393 }
394 }
395
396 /**
397 * @since 4.04.03
398 *
399 * @param array $fields
400 * @param int $order
401 *
402 * @return void
403 */
404 protected function insert_end_section( &$fields, &$order ) {
405 $sub = FrmFieldsHelper::setup_new_vars( 'end_divider' );
406 $sub['name'] = __( 'Section Buttons', 'formidable' );
407 $subs = array( $sub );
408 $this->insert_fields_in_array( $subs, $order, 0, $fields );
409 ++$order;
410 }
411
412 /**
413 * Replace the original combo field with a group.
414 * This switches the name field to individual fields.
415 *
416 * @since 4.04.03
417 *
418 * @param array $subs
419 * @param int $start
420 * @param int $remove
421 * @param array $fields
422 *
423 * @return void
424 */
425 protected function insert_fields_in_array( $subs, $start, $remove, &$fields ) {
426 array_splice( $fields, $start, $remove, $subs );
427 }
428
429 /**
430 * @param string $type
431 * @param array $field
432 * @param string $use Which field type to prefer to consider $field as.
433 * This also eases the recursive use of the method,
434 * particularly the overrides in child classes, as
435 * there will be no need to rebuild the converter
436 * array at usage locations.
437 *
438 * @return string
439 */
440 protected function convert_field_type( $type, $field = array(), $use = '' ) {
441 if ( ! $field ) {
442 // For reverse compatibility.
443 return $type;
444 }
445
446 return $use ? $use : $field['type'];
447 }
448
449 /**
450 * Add the new form to the database and return AJAX data.
451 *
452 * @param array $form Form to import.
453 * @param array $upgrade_omit No field alternative.
454 *
455 * @return array
456 */
457 protected function add_form( $form, $upgrade_omit = array() ) {
458 // Create empty form so we have an ID to work with.
459 $form_id = $this->create_form( $form );
460
461 if ( ! $form_id ) {
462 return $this->form_creation_error_response( $form );
463 }
464
465 $this->create_fields( $form_id, $form );
466
467 $this->create_emails( $form, $form_id );
468
469 $this->track_import( $form['import_form_id'], $form_id );
470
471 // Build and send final AJAX response!
472 return array(
473 'name' => $form['name'],
474 'id' => $form_id,
475 'link' => esc_url_raw( FrmForm::get_edit_link( $form_id ) ),
476 'upgrade_omit' => $this->response['upgrade_omit'],
477 );
478 }
479
480 /**
481 * @since 4.04.03
482 *
483 * @param array $form parameters for the new form to be created. Only
484 * the name key is a must. The keys are the column
485 * names of the forms table in the DB.
486 *
487 * @return bool|int The ID of the newly created form or false on failure.
488 */
489 protected function create_form( $form ) {
490 $form['form_key'] = $form['name'];
491 $form['status'] = 'published';
492
493 return FrmForm::create( $form );
494 }
495
496 /**
497 * @since 4.04.03
498 *
499 * @param array $form
500 *
501 * @return array
502 */
503 protected function form_creation_error_response( $form ) {
504 return array(
505 'error' => true,
506 'name' => sanitize_text_field( $form['name'] ),
507 'msg' => esc_html__( 'There was an error while creating a new form.', 'formidable' ),
508 );
509 }
510
511 /**
512 * @since 4.04.03
513 *
514 * @param int $form_id
515 * @param array $form
516 *
517 * @return void
518 */
519 protected function create_fields( $form_id, &$form ) {
520 foreach ( $form['fields'] as $key => $new_field ) {
521 $new_field['form_id'] = $form_id;
522 $form['fields'][ $key ]['id'] = FrmField::create( $new_field );
523 }
524 }
525
526 /**
527 * @since 4.04.03
528 *
529 * @param array $form
530 * @param int|string $form_id
531 *
532 * @return void
533 */
534 protected function create_emails( $form, $form_id ) {
535 foreach ( $form['actions'] as $action ) {
536 $this->save_action( $action, $form, $form_id );
537 }
538 }
539
540 /**
541 * @since 4.04.03
542 *
543 * @param array $action
544 * @param array $form
545 * @param int $form_id
546 *
547 * @return int|WP_Error
548 */
549 protected function save_action( $action, $form, $form_id ) {
550 /**
551 * @var FrmFormAction
552 */
553 $action_control = FrmFormActionsController::get_form_actions( $action['type'] );
554 unset( $action['type'] );
555 $new_action = $action_control->prepare_new( $form_id );
556
557 foreach ( $action as $key => $value ) {
558 if ( $key === 'post_title' ) {
559 $new_action->post_title = $value;
560 } elseif ( $key === 'ID' ) {
561 $new_action->ID = $value;
562 } elseif ( $key === 'the_post_title' ) {
563 $new_action->post_content['post_title'] = $value;
564 } elseif ( is_string( $value ) ) {
565 $new_action->post_content[ $key ] = $this->replace_smart_tags( $value, $form['fields'] );
566 } else {
567 $new_action->post_content[ $key ] = $value;
568 }
569 }
570
571 return $action_control->save_settings( $new_action );
572 }
573
574 /**
575 * After a form has been successfully imported we track it, so that in the
576 * future we can alert users if they try to import a form that has already
577 * been imported.
578 *
579 * @param int $source_id Imported plugin form ID.
580 * @param int $new_form_id Formidable form ID.
581 *
582 * @return void
583 */
584 protected function track_import( $source_id, $new_form_id ) {
585 $imported = $this->get_tracked_import();
586
587 $imported[ $this->slug ][ $new_form_id ] = $source_id;
588
589 update_option( $this->tracking, $imported, false );
590 }
591
592 /**
593 * @return array
594 */
595 private function get_tracked_import() {
596 return get_option( $this->tracking, array() );
597 }
598
599 /**
600 * @param int $source_id Imported plugin form ID.
601 *
602 * @return int the ID of the created form or 0
603 */
604 private function is_imported( $source_id ) {
605 $imported = $this->get_tracked_import();
606 $new_form_id = 0;
607
608 // phpcs:ignore WordPress.PHP.StrictInArray.MissingTrueStrict
609 if ( ! isset( $imported[ $this->slug ] ) || ! in_array( $source_id, $imported[ $this->slug ] ) ) {
610 return $new_form_id;
611 }
612
613 // phpcs:ignore WordPress.PHP.StrictInArray.MissingTrueStrict
614 $new_form_id = array_search( $source_id, array_reverse( $imported[ $this->slug ], true ) );
615
616 if ( $new_form_id && ! FrmForm::get_key_by_id( $new_form_id ) ) {
617 // Allow reimport if the form was deleted.
618 return 0;
619 }
620
621 return $new_form_id;
622 }
623
624 /** Start functions here that should be overridden **/
625
626 /**
627 * @return array
628 */
629 protected function unsupported_field_types() {
630 return array();
631 }
632
633 /**
634 * @param string $type
635 *
636 * @return bool
637 */
638 private function is_unsupported_field( $type ) {
639 return in_array( $type, $this->unsupported_field_types(), true );
640 }
641
642 /**
643 * Strict PRO fields with no Lite alternatives.
644 *
645 * @return array
646 */
647 protected function skip_pro_fields() {
648 return array();
649 }
650
651 /**
652 * @param string $type
653 *
654 * @return bool
655 */
656 protected function should_skip_field( $type ) {
657 if ( FrmAppHelper::pro_is_installed() ) {
658 return false;
659 }
660
661 return in_array( $type, $this->skip_pro_fields(), true );
662 }
663
664 /**
665 * Replace 3rd-party form provider tags/shortcodes with our own Tags.
666 *
667 * @param string $string String to process the smart tag in.
668 * @param array $fields List of fields for the form.
669 *
670 * @return string
671 */
672 protected function replace_smart_tags( $string, $fields ) {
673 return $string;
674 }
675
676 /**
677 * Get ALL THE FORMS.
678 *
679 * @return array
680 */
681 public function get_forms() {
682 return array();
683 }
684
685 /**
686 * @param int|string $id
687 *
688 * @return array
689 */
690 public function get_form( $id ) {
691 return array();
692 }
693
694 /**
695 * @param array|object $source_form
696 *
697 * @return string
698 */
699 protected function get_form_name( $source_form ) {
700 return __( 'Default Form', 'formidable' );
701 }
702
703 /**
704 * @param array|object $source_form
705 *
706 * @return array
707 */
708 protected function get_form_fields( $source_form ) {
709 return array();
710 }
711
712 /**
713 * @param array $field
714 *
715 * @return string
716 */
717 protected function get_field_type( $field ) {
718 return $field['type'];
719 }
720
721 /**
722 * @param array $field
723 *
724 * @return string
725 */
726 protected function get_field_label( $field ) {
727 $label = $field['label'] ?? '';
728
729 if ( $label ) {
730 return $label;
731 }
732
733 $type = $this->get_field_type( $field );
734 $label = sprintf(
735 /* translators: %1$s - field type */
736 esc_html__( '%1$s Field', 'formidable' ),
737 ucfirst( $type )
738 );
739
740 return trim( $label );
741 }
742 }
743