Completed
Push — master ( c902a3...2661bd )
by Stephanie
02:20
created

FrmFormMigrator::add_form()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 23

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
nc 2
nop 2
dl 0
loc 23
rs 9.552
c 0
b 0
f 0
1
<?php
2
3
abstract class FrmFormMigrator {
4
5
	public $source_active;
6
7
	public $slug;
8
	public $path;
9
	public $name;
10
11
	public $response = array();
12
	public $tracking = 'frm_forms_imported';
13
14
	protected $fields_map          = array();
15
	protected $current_source_form = null;
16
	protected $current_section     = array();
17
18
	/**
19
	 * Define required properties.
20
	 */
21
	public function __construct() {
22
		if ( ! is_admin() ) {
23
			return;
24
		}
25
26
		if ( ! function_exists( 'is_plugin_active' ) ) {
27
			require_once( ABSPATH . '/wp-admin/includes/plugin.php' );
28
		}
29
30
		$this->source_active = is_plugin_active( $this->path );
31
		if ( ! $this->source_active ) {
32
			// if source plugin is not installed, do nothing
33
			return;
34
		}
35
36
		$this->maybe_add_to_import_page();
37
38
		$this->response = array(
39
			'upgrade_omit' => array(),
40
			'unsupported'  => array(),
41
		);
42
	}
43
44
	private function maybe_add_to_import_page() {
45
		add_action( 'frm_import_settings', array( $this, 'import_page' ) );
46
		add_action( 'wp_ajax_frm_import_' . $this->slug, array( $this, 'import_forms' ) );
47
	}
48
49
	public function import_page() {
50
		?>
51
		<div class="wrap">
52
			<h2 class="frm-h2"><?php echo esc_html( $this->name ); ?> Importer</h2>
53
			<p class="howto">Import forms and settings automatically from <?php echo esc_html( $this->name ); ?>.</p>
54
			<div class="welcome-panel" id="welcome-panel">
55
				<div class="welcome-panel-content" style="text-align:center;margin-bottom:10px;">
56
					<p class="about-description">
57
						Select the forms to import.
58
					</p>
59
					<form class="frm_form_importer" method="post"
60
						action="<?php echo esc_url( admin_url( 'admin-ajax.php' ) ); ?>">
61
						<?php wp_nonce_field( 'nonce', 'frm_ajax' ); ?>
62
						<input type="hidden" name="slug" value="<?php echo esc_attr( $this->slug ); ?>" />
63
						<input type="hidden" name="action" value="frm_import_<?php echo esc_attr( $this->slug ); ?>" />
64
						<div style="margin:10px auto;max-width:400px;text-align:left;">
65
							<?php
66
							if ( empty( $this->get_forms() ) ) {
67
								esc_html_e( 'No Forms Found.', 'formidable' );
68
							}
69
							?>
70
							<?php foreach ( $this->get_forms() as $form_id => $name ) { ?>
71
								<p>
72
									<label>
73
										<input type="checkbox" name="form_id[]"
74
											value="<?php echo esc_attr( $form_id ); ?>" checked="checked" />
75
										<?php
76
										echo esc_html( $name );
77
										$new_form_id = $this->is_imported( $form_id );
78
										?>
79
										<?php if ( $new_form_id ) { ?>
80
											(<a href="<?php echo esc_url( FrmForm::get_edit_link( $new_form_id ) ); ?>">previously imported</a>)
81
										<?php } ?>
82
									</label>
83
								</p>
84
							<?php } ?>
85
						</div>
86
						<button type="submit" class="button button-primary button-hero">Start Import</button>
87
					</form>
88
					<div id="frm-importer-process" class="frm-importer-process frm_hidden">
89
90
						<p class="process-count">
91
							<span class="frm-wait" aria-hidden="true"></span>
92
							Importing <span class="form-current">1</span> of <span class="form-total">0</span> forms
93
							from <?php echo esc_html( $this->name ); ?>.
94
						</p>
95
96
						<p class="process-completed" class="frm_hidden">
97
							The import process has finished! We have successfully imported
98
							<span class="forms-completed"></span> forms. You can review the results below.
99
						</p>
100
101
						<div class="status"></div>
102
103
					</div>
104
				</div>
105
			</div>
106
		</div>
107
		<?php
108
	}
109
110
	/**
111
	 * Import all forms using ajax
112
	 */
113
	public function import_forms() {
114
115
		check_ajax_referer( 'frm_ajax', 'nonce' );
116
		FrmAppHelper::permission_check( 'frm_edit_forms' );
117
118
		$forms = FrmAppHelper::get_simple_request(
119
			array(
120
				'param'    => 'form_id',
121
				'type'     => 'post',
122
				'sanitize' => 'absint',
123
			)
124
		);
125
126
		if ( is_array( $forms ) ) {
127
			$imported = array();
128
			foreach ( (array) $forms as $form_id ) {
129
				$imported[] = $this->import_form( $form_id );
130
			}
131
		} else {
132
			$imported = $this->import_form( $forms );
133
		}
134
135
		wp_send_json_success( $imported );
136
	}
137
138
	/**
139
	 * Import a single form
140
	 */
141
	protected function import_form( $source_id ) {
142
143
		$source_form      = $this->get_form( $source_id );
144
		$source_form_name = $this->get_form_name( $source_form );
145
		$source_fields    = $this->get_form_fields( $source_form );
146
		$this->maybe_add_end_fields( $source_fields );
147
148
		$this->current_source_form = $source_form;
149
150
		// If form does not contain fields, bail.
151
		if ( empty( $source_fields ) ) {
152
			wp_send_json_success(
153
				array(
154
					'error' => true,
155
					'name'  => esc_html( $source_form_name ),
156
					'msg'   => __( 'No form fields found.', 'formidable' ),
157
				)
158
			);
159
		}
160
161
		$form = $this->prepare_new_form( $source_id, $source_form_name );
162
163
		$this->prepare_fields( $source_fields, $form );
164
165
		$this->prepare_form( $source_form, $form );
166
167
		$response = $this->add_form( $form );
168
169
		// reset
170
		$this->current_source_form = null;
171
172
		return $response;
173
	}
174
175
	protected function prepare_new_form( $source_id, $source_form_name ) {
176
		return array(
177
			'import_form_id' => $source_id,
178
			'fields'         => array(),
179
			'name'           => $source_form_name,
180
			'description'    => '',
181
			'options'        => array(),
182
			'actions'        => array(),
183
		);
184
	}
185
186
	protected function prepare_form( $form, &$new_form ) {
187
		// customize this function
188
	}
189
190
	protected function prepare_fields( $fields, &$form ) {
191
		$field_order = 1;
192
193
		foreach ( $fields as $field ) {
194
			$field = (array) $field;
195
196
			$label = $this->get_field_label( $field );
197
			$type  = $this->get_field_type( $field );
198
199
			// check if field is unsupported. If unsupported make note and continue
200
			if ( $this->is_unsupported_field( $type ) ) {
201
				$this->response['unsupported'][] = $label;
202
				continue;
203
			}
204
205
			if ( $this->should_skip_field( $type ) ) {
206
				$this->response['upgrade_omit'][] = $label;
207
				continue;
208
			}
209
210
			$new_type = $this->convert_field_type( $type, $field );
211
212
			$new_field                = FrmFieldsHelper::setup_new_vars( $new_type );
213
			$new_field['name']        = $label;
214
			$new_field['field_order'] = $field_order;
215
			$new_field['original']    = $type;
216
217
			$this->prepare_field( $field, $new_field );
218
219
			$in_section = ! empty( $this->current_section ) && ! in_array( $new_type, $this->fields_with_end() ) && $new_type !== 'break';
220
			if ( $in_section ) {
221
				$new_field['field_options']['in_section'] = $this->current_section['id'];
222
			}
223
224
			$form['fields'][] = $new_field;
225
226
			if ( in_array( $new_type, $this->fields_with_end() ) ) {
227
				$this->current_section = $field;
228
			} elseif ( $new_type === 'break' || $new_type === 'end_divider' ) {
229
				$this->current_section = array();
230
			}
231
232
			// This may occassionally skip one level/order e.g. after adding a
233
			// list field, as field_order would already be prepared to be used.
234
			$field_order ++;
235
236
			if ( isset( $new_field['fields'] ) && is_array( $new_field['fields'] ) && ! empty( $new_field['fields'] ) ) {
237
				// we have (inner) fields to merge
238
239
				$form['fields'] = array_merge( $form['fields'], $new_field['fields'] );
240
				// set the new field_order as it would have changed
241
				$field_order    = $new_field['current_order'];
242
			}
243
		}
244
	}
245
246
	protected function prepare_field( $field, &$new_field ) {
247
		// customize this function
248
	}
249
250
	/**
251
	 * Add any field types that will need an end section field.
252
	 *
253
	 * @since 4.04.03
254
	 */
255
	protected function fields_with_end() {
256
		return array( 'divider' );
257
	}
258
259
	/**
260
	 * @since 4.04.03
261
	 */
262
	protected function maybe_add_end_fields( &$fields ) {
263
		$with_end = $this->fields_with_end();
264
		if ( empty( $with_end ) ) {
265
			return;
266
		}
267
268
		$open = array();
269
270
		$order = 0;
271
		foreach ( $fields as $field ) {
272
			$order ++;
273
			$type     = $this->get_field_type( $field );
274
			$new_type = $this->convert_field_type( $type, $field );
275
			if ( ! in_array( $new_type, $with_end ) && $new_type !== 'break' ) {
276
				continue;
277
			}
278
279
			if ( ! empty( $open ) ) {
280
				$this->insert_end_section( $fields, $order );
281
				$open = array();
282
			}
283
284
			if ( in_array( $new_type, $with_end ) ) {
285
				$open = $field;
286
			}
287
		}
288
289
		if ( ! empty( $open ) ) {
290
			$this->insert_end_section( $fields, $order );
291
		}
292
	}
293
294
	/**
295
	 * @since 4.04.03
296
	 */
297
	protected function insert_end_section( &$fields, &$order ) {
298
		$sub         = FrmFieldsHelper::setup_new_vars( 'end_divider' );
299
		$sub['name'] = __( 'Section Buttons', 'formidable' );
300
		$subs        = array( $sub );
301
		$this->insert_fields_in_array( $subs, $order, 0, $fields );
302
		$order ++;
303
	}
304
305
	/**
306
	 * Replace the original combo field with a group.
307
	 * This switches the name field to individual fields.
308
	 *
309
	 * @since 4.04.03
310
	 */
311
	protected function insert_fields_in_array( $subs, $start, $remove, &$fields ) {
312
		array_splice( $fields, $start, $remove, $subs );
313
	}
314
315
	/**
316
	 * @param string $type
317
	 * @param array  $field
318
	 * @param string $use   Which field type to prefer to consider $field as.
319
	 *                      This also eases the recursive use of the method,
320
	 *                      particularly the overrides in child classes, as
321
	 *                      there will be no need to rebuild the converter
322
	 *                      array at usage locations.
323
	 */
324
	protected function convert_field_type( $type, $field = array(), $use = '' ) {
325
		if ( empty( $field ) ) {
326
			// For reverse compatability.
327
			return $type;
328
		}
329
330
		return $use ? $use : $field['type'];
331
	}
332
333
	/**
334
	 * Add the new form to the database and return AJAX data.å
335
	 *
336
	 * @param array $form Form to import.
337
	 * @param array $upgrade_omit No field alternative
338
	 */
339
	protected function add_form( $form, $upgrade_omit = array() ) {
340
341
		// Create empty form so we have an ID to work with.
342
		$form_id = $this->create_form( $form );
343
344
		if ( empty( $form_id ) ) {
345
			return $this->form_creation_error_response( $form );
346
		}
347
348
		$this->create_fields( $form_id, $form );
0 ignored issues
show
Bug introduced by
It seems like $form_id defined by $this->create_form($form) on line 342 can also be of type boolean; however, FrmFormMigrator::create_fields() does only seem to accept integer, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
349
350
		$this->create_emails( $form );
351
352
		$this->track_import( $form['import_form_id'], $form_id );
0 ignored issues
show
Bug introduced by
It seems like $form_id defined by $this->create_form($form) on line 342 can also be of type boolean; however, FrmFormMigrator::track_import() does only seem to accept integer, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
353
354
		// Build and send final AJAX response!
355
		return array(
356
			'name'         => $form['name'],
357
			'id'           => $form_id,
358
			'link'         => esc_url_raw( FrmForm::get_edit_link( $form_id ) ),
0 ignored issues
show
Bug introduced by
It seems like $form_id defined by $this->create_form($form) on line 342 can also be of type boolean; however, FrmForm::get_edit_link() does only seem to accept integer, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
359
			'upgrade_omit' => $this->response['upgrade_omit'],
360
		);
361
	}
362
363
	/**
364
	 * @since 4.04.03
365
	 *
366
	 * @param array $form parameters for the new form to be created. Only
367
	 *              the name key is a must. The keys are the column
368
	 *              names of the forms table in the DB.
369
	 *
370
	 * @return int The ID of the newly created form.
371
	 */
372
	protected function create_form( $form ) {
373
		$form['form_key'] = $form['name'];
374
		$form['status']   = 'published';
375
376
		return FrmForm::create( $form );
377
	}
378
379
	/**
380
	 * @since 4.04.03
381
	 */
382
	protected function form_creation_error_response( $form ) {
383
		return array(
384
			'error' => true,
385
			'name'  => sanitize_text_field( $form['name'] ),
386
			'msg'   => esc_html__( 'There was an error while creating a new form.', 'formidable' ),
387
		);
388
	}
389
390
	/**
391
	 * @since 4.04.03
392
	 *
393
	 * @param int $form_id
394
	 * @param array $form
395
	 */
396
	protected function create_fields( $form_id, &$form ) {
397
		foreach ( $form['fields'] as $key => $new_field ) {
398
			$new_field['form_id']         = $form_id;
399
			$form['fields'][ $key ]['id'] = FrmField::create( $new_field );
400
		}
401
	}
402
403
	/**
404
	 * @since 4.04.03
405
	 *
406
	 * @param array $form
407
	 */
408
	protected function create_emails( $form ) {
409
		foreach ( $form['actions'] as $action ) {
410
			$action_control = FrmFormActionsController::get_form_actions( $action['type'] );
411
			unset( $action['type'] );
412
			$new_action = $action_control->prepare_new( $form_id );
0 ignored issues
show
Bug introduced by
The variable $form_id does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
413
			foreach ( $action as $key => $value ) {
414
				if ( $key === 'post_title' ) {
415
					$new_action->post_title = $value;
416
				} else {
417
					$new_action->post_content[ $key ] = $this->replace_smart_tags( $value, $form['fields'] );
418
				}
419
			}
420
421
			$action_control->save_settings( $new_action );
422
		}
423
	}
424
425
	/**
426
	 * After a form has been successfully imported we track it, so that in the
427
	 * future we can alert users if they try to import a form that has already
428
	 * been imported.
429
	 *
430
	 * @param int $source_id Imported plugin form ID
431
	 * @param int $new_form_id Formidable form ID
432
	 */
433
	protected function track_import( $source_id, $new_form_id ) {
434
435
		$imported = $this->get_tracked_import();
436
437
		$imported[ $this->slug ][ $new_form_id ] = $source_id;
438
439
		update_option( $this->tracking, $imported, false );
440
	}
441
442
	/**
443
	 * @return array
444
	 */
445
	private function get_tracked_import() {
446
		return get_option( $this->tracking, array() );
447
	}
448
449
	/**
450
	 * @param int $source_id Imported plugin form ID
451
	 *
452
	 * @return int the ID of the created form or 0
453
	 */
454
	private function is_imported( $source_id ) {
455
		$imported    = $this->get_tracked_import();
456
		$new_form_id = 0;
457
		if ( ! isset( $imported[ $this->slug ] ) || ! in_array( $source_id, $imported[ $this->slug ] ) ) {
458
			return $new_form_id;
459
		}
460
461
		$new_form_id = array_search( $source_id, array_reverse( $imported[ $this->slug ], true ) );
462
		if ( ! empty( $new_form_id ) && empty( FrmForm::get_key_by_id( $new_form_id ) ) ) {
463
			// Allow reimport if the form was deleted.
464
			$new_form_id = 0;
465
		}
466
467
		return $new_form_id;
468
	}
469
470
	/** Start functions here that should be overridden **/
471
472
	/**
473
	 * @return array
474
	 */
475
	protected function unsupported_field_types() {
476
		return array();
477
	}
478
479
	private function is_unsupported_field( $type ) {
480
		$fields = $this->unsupported_field_types();
481
482
		return in_array( $type, $fields, true );
483
	}
484
485
	/**
486
	 * Strict PRO fields with no Lite alternatives.
487
	 *
488
	 * @return array
489
	 */
490
	protected function skip_pro_fields() {
491
		return array();
492
	}
493
494
	protected function should_skip_field( $type ) {
495
		$skip_pro_fields = $this->skip_pro_fields();
496
497
		return ( ! FrmAppHelper::pro_is_installed() && in_array( $type, $skip_pro_fields, true ) );
498
	}
499
500
	/**
501
	 * Replace 3rd-party form provider tags/shortcodes with our own Tags.
502
	 *
503
	 * @param string $string String to process the smart tag in.
504
	 * @param array $fields List of fields for the form.
505
	 *
506
	 * @return string
507
	 */
508
	protected function replace_smart_tags( $string, $fields ) {
509
		return $string;
510
	}
511
512
	/**
513
	 * Get ALL THE FORMS.
514
	 *
515
	 * @return array
516
	 */
517
	public function get_forms() {
518
		return array();
519
	}
520
521
	public function get_form( $id ) {
522
		return array();
523
	}
524
525
	/**
526
	 * @param object|array $source_form
527
	 *
528
	 * @return string
529
	 */
530
	protected function get_form_name( $source_form ) {
531
		return __( 'Default Form', 'formidable' );
532
	}
533
534
	/**
535
	 * @param object|array $source_form
536
	 *
537
	 * @return array
538
	 */
539
	protected function get_form_fields( $source_form ) {
540
		return array();
541
	}
542
543
	/**
544
	 * @param array $field
545
	 *
546
	 * @return string
547
	 */
548
	protected function get_field_type( $field ) {
549
		return $field['type'];
550
	}
551
552
	/**
553
	 * @param array $field
554
	 *
555
	 * @return string
556
	 */
557
	protected function get_field_label( $field ) {
558
		$label = isset( $field['label'] ) ? $field['label'] : '';
559
		if ( ! empty( $label ) ) {
560
			return $label;
561
		}
562
563
		$type  = $this->get_field_type( $field );
564
		$label = sprintf(
565
			/* translators: %1$s - field type */
566
			esc_html__( '%1$s Field', 'formidable' ),
567
			ucfirst( $type )
568
		);
569
570
		return trim( $label );
571
	}
572
}
573