Completed
Push — master ( 2661bd...c768e3 )
by Stephanie
07:09
created

FrmFormMigrator::save_action()   A

Complexity

Conditions 5
Paths 4

Size

Total Lines 18

Duplication

Lines 0
Ratio 0 %

Importance

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