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

744 lines 17.0 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 ?>
94 <div class="wrap">
95 <h2 class="frm-h2"><?php echo esc_html( $this->name ); ?> Importer</h2>
96 <p class="howto">Import forms and settings automatically from <?php echo esc_html( $this->name ); ?>.</p>
97 <div id="welcome-panel">
98 <div style="margin-bottom:10px;">
99 <p class="about-description">
100 Select the forms to import.
101 </p>
102 <form class="frm_form_importer" method="post"
103 action="<?php echo esc_url( admin_url( 'admin-ajax.php' ) ); ?>">
104 <?php wp_nonce_field( 'nonce', 'frm_ajax' ); ?>
105 <input type="hidden" name="slug" value="<?php echo esc_attr( $this->slug ); ?>" />
106 <input type="hidden" name="action" value="frm_import_<?php echo esc_attr( $this->slug ); ?>" />
107 <div style="max-width:400px;text-align:left;">
108 <?php
109 if ( ! $forms ) {
110 esc_html_e( 'No Forms Found.', 'formidable' );
111 }
112 ?>
113 <?php foreach ( $forms as $form_id => $name ) { ?>
114 <p>
115 <label>
116 <input type="checkbox" name="form_id[]"
117 value="<?php echo esc_attr( $form_id ); ?>" checked="checked" />
118 <?php
119 echo esc_html( $name );
120 $new_form_id = $this->is_imported( $form_id );
121 ?>
122 <?php if ( $new_form_id ) { ?>
123 (<a href="<?php echo esc_url( FrmForm::get_edit_link( $new_form_id ) ); ?>">previously imported</a>)
124 <?php } ?>
125 </label>
126 </p>
127 <?php } ?>
128 </div>
129 <?php
130 $button_atts = array(
131 'type' => 'submit',
132 'class' => 'button button-primary frm-button-primary',
133 );
134
135 if ( ! $forms ) {
136 $button_atts['disabled'] = 'disabled';
137 }
138 ?>
139 <p class="submit">
140 <button <?php FrmAppHelper::array_to_html_params( $button_atts, true ); ?>>
141 <?php esc_html_e( 'Start Import', 'formidable' ); ?>
142 </button>
143 </p>
144 </form>
145 <div id="frm-importer-process" class="frm-importer-process frm_hidden">
146
147 <p class="process-count">
148 <span class="frm-wait" aria-hidden="true"></span>
149 Importing <span class="form-current">1</span> of <span class="form-total">0</span> forms
150 from <?php echo esc_html( $this->name ); ?>.
151 </p>
152
153 <p class="process-completed" class="frm_hidden">
154 <?php
155 printf(
156 // translators: %s is the number of forms that were imported.
157 esc_html__( 'The import process has finished! We have successfully imported %s forms. You can review the results below.', 'formidable' ),
158 '<span class="forms-completed"></span>'
159 );
160 ?>
161 </p>
162
163 <div class="status"></div>
164
165 </div>
166 </div>
167 </div>
168 </div>
169 <?php
170 }
171
172 /**
173 * Import all forms using ajax
174 *
175 * @return void
176 */
177 public function import_forms() {
178
179 check_ajax_referer( 'frm_ajax', 'nonce' );
180 FrmAppHelper::permission_check( 'frm_edit_forms' );
181
182 $forms = FrmAppHelper::get_simple_request(
183 array(
184 'param' => 'form_id',
185 'type' => 'post',
186 'sanitize' => 'absint',
187 )
188 );
189
190 if ( is_array( $forms ) ) {
191 $imported = array();
192
193 foreach ( $forms as $form_id ) {
194 $imported[] = $this->import_form( $form_id );
195 }
196 } else {
197 $imported = $this->import_form( $forms );
198 }
199
200 wp_send_json_success( $imported );
201 }
202
203 /**
204 * Import a single form
205 *
206 * @param int $source_id Source form ID.
207 *
208 * @return array
209 */
210 protected function import_form( $source_id ) {
211
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 ( empty( $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
283 $label = $this->get_field_label( $field );
284 $type = $this->get_field_type( $field );
285
286 // check if field is unsupported. If unsupported make note and continue
287 if ( $this->is_unsupported_field( $type ) ) {
288 $this->response['unsupported'][] = $label;
289 continue;
290 }
291
292 if ( $this->should_skip_field( $type ) ) {
293 $this->response['upgrade_omit'][] = $label;
294 continue;
295 }
296
297 $new_type = $this->convert_field_type( $type, $field );
298
299 $new_field = FrmFieldsHelper::setup_new_vars( $new_type );
300 $new_field['name'] = $label;
301 $new_field['field_order'] = $field_order;
302 $new_field['original'] = $type;
303
304 $this->prepare_field( $field, $new_field );
305
306 $in_section = ! empty( $this->current_section ) && ! in_array( $new_type, $this->fields_with_end() ) && $new_type !== 'break';
307
308 if ( $in_section ) {
309 $new_field['field_options']['in_section'] = $this->current_section['id'];
310 }
311
312 $form['fields'][] = $new_field;
313
314 if ( in_array( $new_type, $this->fields_with_end() ) ) {
315 $this->current_section = $field;
316 } elseif ( $new_type === 'break' || $new_type === 'end_divider' ) {
317 $this->current_section = array();
318 }
319
320 // This may occasionally skip one level/order e.g. after adding a
321 // list field, as field_order would already be prepared to be used.
322 ++$field_order;
323
324 if ( ! empty( $new_field['fields'] ) && is_array( $new_field['fields'] ) ) {
325 // we have (inner) fields to merge
326
327 $form['fields'] = array_merge( $form['fields'], $new_field['fields'] );
328 // set the new field_order as it would have changed
329 $field_order = $new_field['current_order'];
330 }
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 ( empty( $with_end ) ) {
366 return;
367 }
368
369 $open = array();
370
371 $order = 0;
372
373 foreach ( $fields as $field ) {
374 ++$order;
375 $type = $this->get_field_type( $field );
376 $new_type = $this->convert_field_type( $type, $field );
377
378 if ( ! in_array( $new_type, $with_end ) && $new_type !== 'break' ) {
379 continue;
380 }
381
382 if ( ! empty( $open ) ) {
383 $this->insert_end_section( $fields, $order );
384 $open = array();
385 }
386
387 if ( in_array( $new_type, $with_end ) ) {
388 $open = $field;
389 }
390 }
391
392 if ( ! empty( $open ) ) {
393 $this->insert_end_section( $fields, $order );
394 }
395 }
396
397 /**
398 * @since 4.04.03
399 *
400 * @param array $fields
401 * @param int $order
402 *
403 * @return void
404 */
405 protected function insert_end_section( &$fields, &$order ) {
406 $sub = FrmFieldsHelper::setup_new_vars( 'end_divider' );
407 $sub['name'] = __( 'Section Buttons', 'formidable' );
408 $subs = array( $sub );
409 $this->insert_fields_in_array( $subs, $order, 0, $fields );
410 ++$order;
411 }
412
413 /**
414 * Replace the original combo field with a group.
415 * This switches the name field to individual fields.
416 *
417 * @since 4.04.03
418 *
419 * @param array $subs
420 * @param int $start
421 * @param int $remove
422 * @param array $fields
423 *
424 * @return void
425 */
426 protected function insert_fields_in_array( $subs, $start, $remove, &$fields ) {
427 array_splice( $fields, $start, $remove, $subs );
428 }
429
430 /**
431 * @param string $type
432 * @param array $field
433 * @param string $use Which field type to prefer to consider $field as.
434 * This also eases the recursive use of the method,
435 * particularly the overrides in child classes, as
436 * there will be no need to rebuild the converter
437 * array at usage locations.
438 *
439 * @return string
440 */
441 protected function convert_field_type( $type, $field = array(), $use = '' ) {
442 if ( empty( $field ) ) {
443 // For reverse compatibility.
444 return $type;
445 }
446
447 return $use ? $use : $field['type'];
448 }
449
450 /**
451 * Add the new form to the database and return AJAX data.
452 *
453 * @param array $form Form to import.
454 * @param array $upgrade_omit No field alternative.
455 *
456 * @return array
457 */
458 protected function add_form( $form, $upgrade_omit = array() ) {
459
460 // Create empty form so we have an ID to work with.
461 $form_id = $this->create_form( $form );
462
463 if ( empty( $form_id ) ) {
464 return $this->form_creation_error_response( $form );
465 }
466
467 $this->create_fields( $form_id, $form );
468
469 $this->create_emails( $form, $form_id );
470
471 $this->track_import( $form['import_form_id'], $form_id );
472
473 // Build and send final AJAX response!
474 return array(
475 'name' => $form['name'],
476 'id' => $form_id,
477 'link' => esc_url_raw( FrmForm::get_edit_link( $form_id ) ),
478 'upgrade_omit' => $this->response['upgrade_omit'],
479 );
480 }
481
482 /**
483 * @since 4.04.03
484 *
485 * @param array $form parameters for the new form to be created. Only
486 * the name key is a must. The keys are the column
487 * names of the forms table in the DB.
488 *
489 * @return bool|int The ID of the newly created form or false on failure.
490 */
491 protected function create_form( $form ) {
492 $form['form_key'] = $form['name'];
493 $form['status'] = 'published';
494
495 return FrmForm::create( $form );
496 }
497
498 /**
499 * @since 4.04.03
500 *
501 * @param array $form
502 *
503 * @return array
504 */
505 protected function form_creation_error_response( $form ) {
506 return array(
507 'error' => true,
508 'name' => sanitize_text_field( $form['name'] ),
509 'msg' => esc_html__( 'There was an error while creating a new form.', 'formidable' ),
510 );
511 }
512
513 /**
514 * @since 4.04.03
515 *
516 * @param int $form_id
517 * @param array $form
518 *
519 * @return void
520 */
521 protected function create_fields( $form_id, &$form ) {
522 foreach ( $form['fields'] as $key => $new_field ) {
523 $new_field['form_id'] = $form_id;
524 $form['fields'][ $key ]['id'] = FrmField::create( $new_field );
525 }
526 }
527
528 /**
529 * @since 4.04.03
530 *
531 * @param array $form
532 * @param int|string $form_id
533 *
534 * @return void
535 */
536 protected function create_emails( $form, $form_id ) {
537 foreach ( $form['actions'] as $action ) {
538 $this->save_action( $action, $form, $form_id );
539 }
540 }
541
542 /**
543 * @since 4.04.03
544 *
545 * @param array $action
546 * @param array $form
547 * @param int $form_id
548 *
549 * @return int|WP_Error
550 */
551 protected function save_action( $action, $form, $form_id ) {
552 /**
553 * @var FrmFormAction
554 */
555 $action_control = FrmFormActionsController::get_form_actions( $action['type'] );
556 unset( $action['type'] );
557 $new_action = $action_control->prepare_new( $form_id );
558
559 foreach ( $action as $key => $value ) {
560 if ( $key === 'post_title' ) {
561 $new_action->post_title = $value;
562 } elseif ( $key === 'ID' ) {
563 $new_action->ID = $value;
564 } elseif ( $key === 'the_post_title' ) {
565 $new_action->post_content['post_title'] = $value;
566 } elseif ( is_string( $value ) ) {
567 $new_action->post_content[ $key ] = $this->replace_smart_tags( $value, $form['fields'] );
568 } else {
569 $new_action->post_content[ $key ] = $value;
570 }
571 }
572
573 return $action_control->save_settings( $new_action );
574 }
575
576 /**
577 * After a form has been successfully imported we track it, so that in the
578 * future we can alert users if they try to import a form that has already
579 * been imported.
580 *
581 * @param int $source_id Imported plugin form ID.
582 * @param int $new_form_id Formidable form ID.
583 *
584 * @return void
585 */
586 protected function track_import( $source_id, $new_form_id ) {
587
588 $imported = $this->get_tracked_import();
589
590 $imported[ $this->slug ][ $new_form_id ] = $source_id;
591
592 update_option( $this->tracking, $imported, false );
593 }
594
595 /**
596 * @return array
597 */
598 private function get_tracked_import() {
599 return get_option( $this->tracking, array() );
600 }
601
602 /**
603 * @param int $source_id Imported plugin form ID.
604 *
605 * @return int the ID of the created form or 0
606 */
607 private function is_imported( $source_id ) {
608 $imported = $this->get_tracked_import();
609 $new_form_id = 0;
610
611 if ( ! isset( $imported[ $this->slug ] ) || ! in_array( $source_id, $imported[ $this->slug ] ) ) {
612 return $new_form_id;
613 }
614
615 $new_form_id = array_search( $source_id, array_reverse( $imported[ $this->slug ], true ) );
616
617 if ( ! empty( $new_form_id ) && empty( FrmForm::get_key_by_id( $new_form_id ) ) ) {
618 // Allow reimport if the form was deleted.
619 $new_form_id = 0;
620 }
621
622 return $new_form_id;
623 }
624
625 /** Start functions here that should be overridden **/
626
627 /**
628 * @return array
629 */
630 protected function unsupported_field_types() {
631 return array();
632 }
633
634 /**
635 * @param string $type
636 *
637 * @return bool
638 */
639 private function is_unsupported_field( $type ) {
640 $fields = $this->unsupported_field_types();
641
642 return in_array( $type, $fields, true );
643 }
644
645 /**
646 * Strict PRO fields with no Lite alternatives.
647 *
648 * @return array
649 */
650 protected function skip_pro_fields() {
651 return array();
652 }
653
654 /**
655 * @param string $type
656 *
657 * @return bool
658 */
659 protected function should_skip_field( $type ) {
660 $skip_pro_fields = $this->skip_pro_fields();
661
662 return ( ! FrmAppHelper::pro_is_installed() && in_array( $type, $skip_pro_fields, true ) );
663 }
664
665 /**
666 * Replace 3rd-party form provider tags/shortcodes with our own Tags.
667 *
668 * @param string $string String to process the smart tag in.
669 * @param array $fields List of fields for the form.
670 *
671 * @return string
672 */
673 protected function replace_smart_tags( $string, $fields ) {
674 return $string;
675 }
676
677 /**
678 * Get ALL THE FORMS.
679 *
680 * @return array
681 */
682 public function get_forms() {
683 return array();
684 }
685
686 /**
687 * @param int|string $id
688 *
689 * @return array
690 */
691 public function get_form( $id ) {
692 return array();
693 }
694
695 /**
696 * @param array|object $source_form
697 *
698 * @return string
699 */
700 protected function get_form_name( $source_form ) {
701 return __( 'Default Form', 'formidable' );
702 }
703
704 /**
705 * @param array|object $source_form
706 *
707 * @return array
708 */
709 protected function get_form_fields( $source_form ) {
710 return array();
711 }
712
713 /**
714 * @param array $field
715 *
716 * @return string
717 */
718 protected function get_field_type( $field ) {
719 return $field['type'];
720 }
721
722 /**
723 * @param array $field
724 *
725 * @return string
726 */
727 protected function get_field_label( $field ) {
728 $label = $field['label'] ?? '';
729
730 if ( ! empty( $label ) ) {
731 return $label;
732 }
733
734 $type = $this->get_field_type( $field );
735 $label = sprintf(
736 /* translators: %1$s - field type */
737 esc_html__( '%1$s Field', 'formidable' ),
738 ucfirst( $type )
739 );
740
741 return trim( $label );
742 }
743 }
744