Completed
Push — master ( 7068a6...d22f64 )
by Stephanie
03:12
created

FrmAddon::load_hooks()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 0
dl 0
loc 4
rs 10
c 0
b 0
f 0
1
<?php
2
3
if ( ! defined( 'ABSPATH' ) ) {
4
	die( 'You are not allowed to call this page directly.' );
5
}
6
7
class FrmAddon {
8
	public $store_url = 'https://formidableforms.com';
9
	public $download_id;
10
	public $plugin_file;
11
	public $plugin_folder;
12
	public $plugin_name;
13
	public $plugin_slug;
14
	public $option_name;
15
	public $version;
16
	public $author = 'Strategy11';
17
	public $is_parent_licence = false;
18
	private $is_expired_addon = false;
19
	public $license;
20
	protected $get_beta = false;
21
22
	public function __construct() {
23
24
		if ( empty( $this->plugin_slug ) ) {
25
			$this->plugin_slug = preg_replace( '/[^a-zA-Z0-9_\s]/', '', str_replace( ' ', '_', strtolower( $this->plugin_name ) ) );
26
		}
27
		if ( empty( $this->option_name ) ) {
28
			$this->option_name = 'edd_' . $this->plugin_slug . '_license_';
29
		}
30
31
		$this->plugin_folder = plugin_basename( $this->plugin_file );
32
		$this->license = $this->get_license();
33
34
		add_filter( 'frm_installed_addons', array( &$this, 'insert_installed_addon' ) );
35
		$this->edd_plugin_updater();
36
	}
37
38
	public static function load_hooks() {
39
		add_filter( 'frm_include_addon_page', '__return_true' );
40
		//new static();
41
	}
42
43
	public function insert_installed_addon( $plugins ) {
44
		$plugins[ $this->plugin_slug ] = $this;
45
		return $plugins;
46
	}
47
48
	public static function get_addon( $plugin_slug ) {
49
		$plugins = apply_filters( 'frm_installed_addons', array() );
50
		$plugin = false;
51
		if ( isset( $plugins[ $plugin_slug ] ) ) {
52
			$plugin = $plugins[ $plugin_slug ];
53
		}
54
		return $plugin;
55
	}
56
57
	public function edd_plugin_updater() {
58
59
		$this->is_license_revoked();
60
		$license = $this->license;
61
62
		add_action( 'after_plugin_row_' . plugin_basename( $this->plugin_file ), array( $this, 'maybe_show_license_message' ), 10, 2 );
63
64
		if ( ! empty( $license ) ) {
65
66
			if ( 'formidable/formidable.php' !== $this->plugin_folder ) {
67
				add_filter( 'plugins_api', array( &$this, 'plugins_api_filter' ), 10, 3 );
68
			}
69
70
			add_filter( 'site_transient_update_plugins', array( &$this, 'clear_expired_download' ) );
71
		}
72
	}
73
74
	/**
75
	 * Updates information on the "View version x.x details" page with custom data.
76
	 *
77
	 * @uses api_request()
78
	 *
79
	 * @param mixed   $_data
80
	 * @param string  $_action
81
	 * @param object  $_args
82
	 * @return object $_data
83
	 */
84
	public function plugins_api_filter( $_data, $_action = '', $_args = null ) {
85
86
		if ( $_action != 'plugin_information' ) {
87
			return $_data;
88
		}
89
90
		$slug = basename( $this->plugin_file, '.php' );
91
		if ( ! isset( $_args->slug ) || $_args->slug != $slug ) {
92
			return $_data;
93
		}
94
95
		$item_id = $this->download_id;
96
		if ( empty( $item_id ) ) {
97
			$_data = array(
98
				'name'      => $this->plugin_name,
99
				'excerpt'   => '',
100
				'changelog' => 'See the full changelog at <a href="' . esc_url( $this->store_url . '/changelog/' ) . '"></a>',
101
	 			'banners' => array(
102
	 				'high' => '',
103
	 				'low'  => 'https://ps.w.org/formidable/assets/banner-1544x500.png',
104
	 			),
105
			);
106
		} else {
107
			$plugins = FrmAddonsController::get_addon_info( $this->license );
108
			$_data = $plugins[ $item_id ];
109
		}
110
111
		$_data['sections'] = array(
112
			'description' => $_data['excerpt'],
113
			'changelog'   => $_data['changelog'],
114
		);
115
		$_data['author']   = '<a href="' . esc_url( $this->store_url ) . '">' . esc_html( $this->author ) . '</a>';
116
		$_data['homepage'] = $this->store_url;
117
118
		return (object) $_data;
119
	}
120
121
	public function get_license() {
122
		$license = $this->maybe_get_pro_license();
123
		if ( ! empty( $license ) ) {
124
			return $license;
125
		}
126
127
		$license = trim( get_option( $this->option_name . 'key' ) );
128
		if ( empty( $license ) ) {
129
			$license = $this->activate_defined_license();
130
		}
131
132
		return $license;
133
	}
134
135
	/**
136
	 * @since 3.04.03
137
	 */
138
	protected function maybe_get_pro_license() {
139
		// prevent a loop if $this is the pro plugin
140
		$get_license = FrmAppHelper::pro_is_installed() && is_callable( 'FrmProAppHelper::get_updater' ) && $this->plugin_name != 'Formidable Pro';
141
142
		if ( ! $get_license ) {
143
			return false;
144
		}
145
146
		$frmpro_updater = FrmAddonsController::get_pro_updater();
147
		$license = $frmpro_updater->license;
148
		if ( empty( $license ) ) {
149
			return false;
150
		}
151
152
		$this->get_api_info( $license );
153
		if ( ! $this->is_parent_licence ) {
154
			$license = false;
155
		}
156
157
		return $license;
158
	}
159
160
	/**
161
	 * Activate the license in wp-config.php
162
	 * @since 2.04
163
	 */
164
	public function activate_defined_license() {
165
		$license = $this->get_defined_license();
166
		if ( ! empty( $license ) && ! $this->is_active() && $this->is_time_to_auto_activate() ) {
167
			$response = $this->activate_license( $license );
168
			$this->set_auto_activate_time();
169
			if ( ! $response['success'] ) {
170
				$license = '';
171
			}
172
		}
173
		return $license;
174
	}
175
176
	/**
177
	 * Check the wp-config.php for the license key
178
	 * @since 2.04
179
	 */
180
	public function get_defined_license() {
181
		$consant_name = 'FRM_' . strtoupper( $this->plugin_slug ) . '_LICENSE';
182
		return defined( $consant_name ) ? constant( $consant_name ) : false;
183
	}
184
185
	public function set_license( $license ) {
186
		update_option( $this->option_name . 'key', $license );
187
	}
188
189
	/**
190
	 * If the license is in the config, limit the frequency of checks.
191
	 * The license may be entered incorrectly, so we don't want to check on every page load.
192
	 * @since 2.04
193
	 */
194
	private function is_time_to_auto_activate() {
195
		$last_try = get_option( $this->option_name . 'last_activate' );
196
		return ( ! $last_try || $last_try < strtotime( '-1 day' ) );
197
	}
198
199
	private function set_auto_activate_time() {
200
		update_option( $this->option_name . 'last_activate', time() );
201
	}
202
203
	public function is_active() {
204
		return get_option( $this->option_name . 'active' );
205
	}
206
207
	/**
208
	 * @since 3.04.03
209
	 * @param array error
210
	 */
211
	public function maybe_clear_license( $error ) {
212
		if ( $error['code'] === 'disabled' && $error['license'] === $this->license ) {
213
			$this->clear_license();
214
		}
215
	}
216
217
	public function clear_license() {
218
		delete_option( $this->option_name . 'active' );
219
		delete_option( $this->option_name . 'key' );
220
		delete_site_option( $this->transient_key() );
221
		delete_option( $this->transient_key() );
222
		$this->delete_cache();
223
	}
224
225
	public function set_active( $is_active ) {
226
		update_option( $this->option_name . 'active', $is_active );
227
		$this->delete_cache();
228
		FrmAppHelper::save_combined_js();
229
	}
230
231
	/**
232
	 * @since 3.04.03
233
	 */
234
	protected function delete_cache() {
235
		delete_transient( 'frm_api_licence' );
236
		delete_option( FrmAddonsController::get_cache_key( $this->license ) );
237
	}
238
239
	/**
240
	 * The Pro version includes the show_license_message function.
241
	 * We need an extra check before we allow it to show a message.
242
	 *
243
	 * @since 3.04.03
244
	 */
245
	public function maybe_show_license_message( $file, $plugin ) {
246
		if ( $this->is_expired_addon || isset( $plugin['package'] ) ) {
247
			// let's not show a ton of duplicate messages
248
			return;
249
		}
250
251
		$this->show_license_message( $file, $plugin );
252
	}
253
254
	public function show_license_message( $file, $plugin ) {
255
		$message = '';
256
		if ( empty( $this->license ) ) {
257
			/* translators: %1$s: Plugin name, %2$s: Start link HTML, %3$s: end link HTML */
258
			$message = sprintf( esc_html__( 'Your %1$s license key is missing. Please add it on the %2$slicenses page%3$s.', 'formidable' ), esc_html( $this->plugin_name ), '<a href="' . esc_url( admin_url( 'admin.php?page=formidable-settings&t=licenses_settings' ) ) . '">', '</a>' );
259
		} else {
260
			$errors = FrmAddonsController::error_for_license( $this->license );
261
			if ( ! empty( $errors ) ) {
262
				$message = reset( $errors );
263
			}
264
		}
265
266
		if ( empty( $message ) ) {
267
			return;
268
		}
269
270
		$wp_list_table = _get_list_table( 'WP_Plugins_List_Table' );
271
		$id = sanitize_title( $plugin['Name'] ) . '-next';
272
		echo '<tr class="plugin-update-tr active" id="' . esc_attr( $id ) . '"><td colspan="' . esc_attr( $wp_list_table->get_column_count() ) . '" class="plugin-update colspanchange"><div class="update-message notice error inline notice-error notice-alt"><p>';
273
		echo FrmAppHelper::kses( $message, 'a' ); // WPCS: XSS ok.
274
		echo '<script type="text/javascript">var d = document.getElementById("' . esc_attr( $id ) . '").previousSibling;if ( d !== null ){ d.className = d.className + " update"; }</script>';
275
		echo '</p></div></td></tr>';
276
	}
277
278
	public function clear_expired_download( $transient ) {
279
		if ( ! is_object( $transient ) ) {
280
			return $transient;
281
		}
282
283
		if ( $this->is_current_version( $transient ) ) {
284
			//make sure it doesn't show there is an update if plugin is up-to-date
285
			if ( isset( $transient->response[ $this->plugin_folder ] ) ) {
286
				unset( $transient->response[ $this->plugin_folder ] );
287
			}
288
		} elseif ( isset( $transient->response ) && isset( $transient->response[ $this->plugin_folder ] ) ) {
289
			$this->prepare_update_details( $transient->response[ $this->plugin_folder ] );
290
291
			// if the transient has expired, clear the update and trigger it again
292
			if ( $transient->response[ $this->plugin_folder ] === false ) {
293
				if ( ! $this->has_been_cleared() ) {
294
					$this->cleared_plugins();
295
					$this->manually_queue_update();
296
				}
297
				unset( $transient->response[ $this->plugin_folder ] );
298
			}
299
		}
300
301
		return $transient;
302
	}
303
304
	/**
305
	 * Check if the plugin information is correct to allow an update
306
	 *
307
	 * @since 3.04.03
308
	 * @param object $transient - the current plugin info saved for update
309
	 */
310
	private function prepare_update_details( &$transient ) {
311
		$version_info = $transient;
312
		$has_beta_url = isset( $version_info->beta ) && ! empty( $version_info->beta );
313
		if ( $this->get_beta && ! $has_beta_url ) {
314
			$version_info = (object) $this->get_api_info( $this->license );
315
		}
316
317
		if ( isset( $version_info->new_version ) && ! empty( $version_info->new_version ) ) {
318
			$this->clear_old_plugin_version( $version_info );
319
			if ( $version_info === false ) { // was cleared with timeout
320
				$transient = false;
321
			} else {
322
				$this->maybe_use_beta_url( $version_info );
323
				$version_info->new_version = trim( $version_info->new_version, 'p' );
324
325
				if ( version_compare( $version_info->new_version, $this->version, '>' ) ) {
326
					$transient = $version_info;
327
				}
328
			}
329
		}
330
	}
331
332
	/**
333
	 * Get the API info for this plugin
334
	 *
335
	 * @since 3.04.03
336
	 */
337
	protected function get_api_info( $license ) {
338
		$addons = FrmAddonsController::get_addon_info( $license );
339
		$addon  = FrmAddonsController::get_addon_for_license( $addons, $this );
340
341
		// if there is no download url, this license does not apply to the addon
342
		if ( isset( $addon['package'] ) ) {
343
			$this->is_parent_licence = true;
344
		} elseif ( isset( $addons['error'] ) ) {
345
			// if the license is expired, we must assume all add-ons were packaged
346
			$this->is_parent_licence = true;
347
			$this->is_expired_addon  = true;
348
		}
349
350
		return $addon;
351
	}
352
353
	/**
354
	 * make sure transients don't stick around on sites that
355
	 * don't save the transient expiration
356
	 *
357
	 * @since 2.05.05
358
	 */
359
	private function clear_old_plugin_version( &$version_info ) {
360
		$timeout = ( isset( $version_info->timeout ) && ! empty( $version_info->timeout ) ) ? $version_info->timeout : 0;
361
		if ( ! empty( $timeout ) && time() > $timeout ) {
362
			$version_info = false; // Cache is expired
363
			FrmAddonsController::reset_cached_addons( $this->license );
364
		}
365
	}
366
367
	/**
368
	 * The beta url is always included if the download has a beta.
369
	 * Check if the beta should be downloaded.
370
	 *
371
	 * @since 3.04.03
372
	 */
373
	private function maybe_use_beta_url( &$version_info ) {
374
		if ( $this->get_beta && isset( $version_info->beta ) && ! empty( $version_info->beta ) ) {
375
			$version_info->new_version = $version_info->beta['version'];
376
			$version_info->package     = $version_info->beta['package'];
377
			if ( isset( $version_info->plugin ) && ! empty( $version_info->plugin ) ) {
378
				$version_info->plugin  = $version_info->beta['plugin'];
379
			}
380
		}
381
	}
382
383
	private function is_current_version( $transient ) {
384
		if ( empty( $transient->checked ) || ! isset( $transient->checked[ $this->plugin_folder ] ) ) {
385
			return false;
386
		}
387
388
		$response = ! isset( $transient->response ) || empty( $transient->response );
389
		if ( $response ) {
390
			return true;
391
		}
392
393
		return isset( $transient->response ) && isset( $transient->response[ $this->plugin_folder ] ) && $transient->checked[ $this->plugin_folder ] === $transient->response[ $this->plugin_folder ]->new_version;
394
	}
395
396
	private function has_been_cleared() {
397
		$last_cleared = get_option( 'frm_last_cleared' );
398
		return ( $last_cleared && $last_cleared > date( 'Y-m-d H:i:s', strtotime( '-5 minutes' ) ) );
399
	}
400
401
	private function cleared_plugins() {
402
		update_option( 'frm_last_cleared', date( 'Y-m-d H:i:s' ) );
403
	}
404
405
	private function is_license_revoked() {
406
		if ( empty( $this->license ) || empty( $this->plugin_slug ) || isset( $_POST['license'] ) ) { // WPCS: CSRF ok.
407
			return;
408
		}
409
410
		if ( is_multisite() ) {
411
			$last_checked = get_site_option( $this->transient_key() );
412
		} else {
413
			$last_checked = get_option( $this->transient_key() );
414
		}
415
416
		$seven_days_ago = date( 'Y-m-d H:i:s', strtotime( '-7 days' ) );
417
418
		if ( ! $last_checked || $last_checked < $seven_days_ago ) {
419
			// check weekly
420
			if ( is_multisite() ) {
421
				update_site_option( $this->transient_key(), date( 'Y-m-d H:i:s' ) );
422
			} else {
423
				update_option( $this->transient_key(), date( 'Y-m-d H:i:s' ) );
424
			}
425
426
			$response = $this->get_license_status();
427
			if ( 'revoked' === $response['status'] || 'blocked' === $response['status'] || 'disabled' === $response['status'] ) {
428
				$this->clear_license();
429
			}
430
		}
431
	}
432
433
	private function transient_key() {
434
		return 'frm_' . md5( sanitize_key( $this->license . '_' . $this->plugin_slug ) );
435
	}
436
437
	public static function activate() {
438
		FrmAppHelper::permission_check( 'frm_change_settings' );
439
		check_ajax_referer( 'frm_ajax', 'nonce' );
440
441
		if ( ! isset( $_POST['license'] ) || empty( $_POST['license'] ) ) {
442
			wp_die(
443
				json_encode(
444
					array(
445
						'message' => __( 'Oops! You forgot to enter your license number.', 'formidable' ),
446
						'success' => false,
447
					)
448
				)
449
			);
450
		}
451
452
		$license = stripslashes( sanitize_text_field( $_POST['license'] ) );
453
		$plugin_slug = sanitize_text_field( $_POST['plugin'] );
454
		$this_plugin = self::get_addon( $plugin_slug );
455
		$response = $this_plugin->activate_license( $license );
456
457
		echo json_encode( $response );
458
		wp_die();
459
	}
460
461
	private function activate_license( $license ) {
462
		$this->set_license( $license );
463
		$this->license = $license;
464
465
		$response = $this->get_license_status();
466
		$response['message'] = '';
467
		$response['success'] = false;
468
469
		if ( $response['error'] ) {
470
			$response['message'] = $response['status'];
471
		} else {
472
			$messages = $this->get_messages();
473
			if ( is_string( $response['status'] ) && isset( $messages[ $response['status'] ] ) ) {
474
				$response['message'] = $messages[ $response['status'] ];
475
			} else {
476
				$response['message'] = FrmAppHelper::kses( $response['status'], array( 'a' ) );
477
			}
478
479
			$is_valid = false;
480
			if ( 'valid' === $response['status'] ) {
481
				$is_valid = 'valid';
482
				$response['success'] = true;
483
			}
484
			$this->set_active( $is_valid );
485
		}
486
487
		return $response;
488
	}
489
490
	private function get_license_status() {
491
		$response = array(
492
			'status' => 'missing',
493
			'error'  => true,
494
		);
495
		if ( empty( $this->license ) ) {
496
			$response['error'] = false;
497
			return $response;
498
		}
499
500
		try {
501
			$response['error'] = false;
502
			$license_data = $this->send_mothership_request( 'activate_license' );
503
504
			// $license_data->license will be either "valid" or "invalid"
505
			if ( is_array( $license_data ) ) {
506
				if ( in_array( $license_data['license'], array( 'valid', 'invalid' ), true ) ) {
507
					$response['status'] = $license_data['license'];
508
				}
509
			} else {
510
				$response['status'] = $license_data;
511
			}
512
		} catch ( Exception $e ) {
513
			$response['status'] = $e->getMessage();
514
		}
515
516
		return $response;
517
	}
518
519
	private function get_messages() {
520
		return array(
521
			'valid'   => __( 'Your license has been activated. Enjoy!', 'formidable' ),
522
			'invalid' => __( 'That license key is invalid', 'formidable' ),
523
			'expired' => __( 'That license is expired', 'formidable' ),
524
			'revoked' => __( 'That license has been refunded', 'formidable' ),
525
			'no_activations_left' => __( 'That license has been used on too many sites', 'formidable' ),
526
			'invalid_item_id' => __( 'Oops! That is the wrong license key for this plugin.', 'formidable' ),
527
			'missing' => __( 'That license key is invalid', 'formidable' ),
528
		);
529
	}
530
531
	public static function deactivate() {
532
		FrmAppHelper::permission_check( 'frm_change_settings' );
533
		check_ajax_referer( 'frm_ajax', 'nonce' );
534
535
		$plugin_slug = sanitize_text_field( $_POST['plugin'] );
536
		$this_plugin = self::get_addon( $plugin_slug );
537
		$license = $this_plugin->get_license();
538
		$this_plugin->license = $license;
539
540
		$response = array(
541
			'success' => false,
542
			'message' => '',
543
		);
544
		try {
545
			// $license_data->license will be either "deactivated" or "failed"
546
			$license_data = $this_plugin->send_mothership_request( 'deactivate_license' );
547
			if ( is_array( $license_data ) && 'deactivated' === $license_data['license'] ) {
548
				$response['success'] = true;
549
				$response['message'] = __( 'That license was removed successfully', 'formidable' );
550
			} else {
551
				$response['message'] = __( 'There was an error deactivating your license.', 'formidable' );
552
			}
553
		} catch ( Exception $e ) {
554
			$response['message'] = $e->getMessage();
555
		}
556
557
		$this_plugin->clear_license();
558
559
		echo json_encode( $response );
560
		wp_die();
561
	}
562
563
	public function send_mothership_request( $action ) {
564
		$api_params = array(
565
			'edd_action' => $action,
566
			'license'    => $this->license,
567
			'url'        => home_url(),
568
		);
569
		if ( is_numeric( $this->download_id ) ) {
570
			$api_params['item_id'] = absint( $this->download_id );
571
		} else {
572
			$api_params['item_name'] = rawurlencode( $this->plugin_name );
573
		}
574
575
		$arg_array = array(
576
			'body'      => $api_params,
577
			'timeout'   => 25,
578
			'user-agent' => $this->plugin_slug . '/' . $this->version . '; ' . get_bloginfo( 'url' ),
579
		);
580
581
		$resp = wp_remote_post( $this->store_url, $arg_array );
582
		$body = wp_remote_retrieve_body( $resp );
583
584
		$message = __( 'Your License Key was invalid', 'formidable' );
585
		if ( is_wp_error( $resp ) ) {
586
			$link = FrmAppHelper::admin_upgrade_link( 'api', 'knowledgebase/why-cant-i-activate-formidable-pro/' );
587
			/* translators: %1$s: Start link HTML, %2$s: End link HTML */
588
			$message = sprintf( __( 'You had an error communicating with the Formidable API. %1$sClick here%2$s for more information.', 'formidable' ), '<a href="' . esc_url( $link ) . '" target="_blank">', '</a>' );
589
			$message .= ' ' . $resp->get_error_message();
590
		} elseif ( 'error' === $body || is_wp_error( $body ) ) {
591
			$message = __( 'You had an HTTP error connecting to the Formidable API', 'formidable' );
592
		} else {
593
			$json_res = json_decode( $body, true );
594
			if ( null !== $json_res ) {
595
				if ( is_array( $json_res ) && isset( $json_res['error'] ) ) {
596
					$message = $json_res['error'];
597
				} else {
598
					$message = $json_res;
599
				}
600
			} elseif ( isset( $resp['response'] ) && isset( $resp['response']['code'] ) ) {
601
				/* translators: %1$s: Error code, %2$s: Error message */
602
				$message = sprintf( __( 'There was a %1$s error: %2$s', 'formidable' ), $resp['response']['code'], $resp['response']['message'] . ' ' . $resp['body'] );
0 ignored issues
show
introduced by
Expected next thing to be a escaping function, not '$resp'
Loading history...
603
			}
604
		}
605
606
		return $message;
607
	}
608
609
	public function manually_queue_update() {
610
		set_site_transient( 'update_plugins', null );
611
	}
612
}
613