Completed
Push — master ( 9572ed...c0d504 )
by Stephanie
22s queued 10s
created

FrmAddon::clear_old_plugin_version()   A

Complexity

Conditions 5
Paths 8

Size

Total Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 5
nc 8
nop 1
dl 0
loc 7
rs 9.6111
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, '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
	public function show_license_message( $file, $plugin ) {
240
		if ( $this->is_expired_addon || isset( $plugin['package'] ) ) {
241
			// let's not show a ton of duplicate messages
242
			return;
243
		}
244
245
		$message = '';
246
		if ( empty( $this->license ) ) {
247
			/* translators: %1$s: Plugin name, %2$s: Start link HTML, %3$s: end link HTML */
248
			$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>' );
249
		} else {
250
			$errors = FrmAddonsController::error_for_license( $this->license );
251
			if ( ! empty( $errors ) ) {
252
				$message = reset( $errors );
253
			}
254
		}
255
256
		if ( empty( $message ) ) {
257
			return;
258
		}
259
260
		$wp_list_table = _get_list_table( 'WP_Plugins_List_Table' );
261
		$id = sanitize_title( $plugin['Name'] ) . '-next';
262
		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>';
263
		echo FrmAppHelper::kses( $message, 'a' ); // WPCS: XSS ok.
264
		echo '<script type="text/javascript">var d = document.getElementById("' . esc_attr( $id ) . '").previousSibling;if ( d !== null ){ d.className = d.className + " update"; }</script>';
265
		echo '</p></div></td></tr>';
266
	}
267
268
	public function clear_expired_download( $transient ) {
269
		if ( ! is_object( $transient ) ) {
270
			return $transient;
271
		}
272
273
		if ( $this->is_current_version( $transient ) ) {
274
			//make sure it doesn't show there is an update if plugin is up-to-date
275
			if ( isset( $transient->response[ $this->plugin_folder ] ) ) {
276
				unset( $transient->response[ $this->plugin_folder ] );
277
			}
278
		} elseif ( isset( $transient->response ) && isset( $transient->response[ $this->plugin_folder ] ) ) {
279
			$this->prepare_update_details( $transient->response[ $this->plugin_folder ] );
280
281
			// if the transient has expired, clear the update and trigger it again
282
			if ( $transient->response[ $this->plugin_folder ] === false ) {
283
				if ( ! $this->has_been_cleared() ) {
284
					$this->cleared_plugins();
285
					$this->manually_queue_update();
286
				}
287
				unset( $transient->response[ $this->plugin_folder ] );
288
			}
289
		}
290
291
		return $transient;
292
	}
293
294
	/**
295
	 * Check if the plugin information is correct to allow an update
296
	 *
297
	 * @since 3.04.03
298
	 * @param object $transient - the current plugin info saved for update
299
	 */
300
	private function prepare_update_details( &$transient ) {
301
		$version_info = $transient;
302
		$has_beta_url = isset( $version_info->beta ) && ! empty( $version_info->beta );
303
		if ( $this->get_beta && ! $has_beta_url ) {
304
			$version_info = (object) $this->get_api_info( $this->license );
305
		}
306
307
		if ( isset( $version_info->new_version ) && ! empty( $version_info->new_version ) ) {
308
			$this->clear_old_plugin_version( $version_info );
309
			if ( $version_info === false ) { // was cleared with timeout
310
				$transient = false;
311
			} else {
312
				$this->maybe_use_beta_url( $version_info );
313
				$version_info->new_version = trim( $version_info->new_version, 'p' );
314
315
				if ( version_compare( $version_info->new_version, $this->version, '>' ) ) {
316
					$transient = $version_info;
317
				}
318
			}
319
		}
320
	}
321
322
	/**
323
	 * Get the API info for this plugin
324
	 *
325
	 * @since 3.04.03
326
	 */
327
	protected function get_api_info( $license ) {
328
		$addons = FrmAddonsController::get_addon_info( $license );
329
		$addon  = FrmAddonsController::get_addon_for_license( $addons, $this );
330
331
		// if there is no download url, this license does not apply to the addon
332
		if ( isset( $addon['package'] ) ) {
333
			$this->is_parent_licence = true;
334
		} elseif ( isset( $addons['error'] ) ) {
335
			// if the license is expired, we must assume all add-ons were packaged
336
			$this->is_parent_licence = true;
337
			$this->is_expired_addon  = true;
338
		}
339
340
		return $addon;
341
	}
342
343
	/**
344
	 * make sure transients don't stick around on sites that
345
	 * don't save the transient expiration
346
	 *
347
	 * @since 2.05.05
348
	 */
349
	private function clear_old_plugin_version( &$version_info ) {
350
		$timeout = ( isset( $version_info->timeout ) && ! empty( $version_info->timeout ) ) ? $version_info->timeout : 0;
351
		if ( ! empty( $timeout ) && time() > $timeout ) {
352
			$version_info = false; // Cache is expired
353
			FrmAddonsController::reset_cached_addons( $this->license );
354
		}
355
	}
356
357
	/**
358
	 * The beta url is always included if the download has a beta.
359
	 * Check if the beta should be downloaded.
360
	 *
361
	 * @since 3.04.03
362
	 */
363
	private function maybe_use_beta_url( &$version_info ) {
364
		if ( $this->get_beta && isset( $version_info->beta ) && ! empty( $version_info->beta ) ) {
365
			$version_info->new_version = $version_info->beta['version'];
366
			$version_info->package     = $version_info->beta['package'];
367
			if ( isset( $version_info->plugin ) && ! empty( $version_info->plugin ) ) {
368
				$version_info->plugin  = $version_info->beta['plugin'];
369
			}
370
		}
371
	}
372
373
	private function is_current_version( $transient ) {
374
		if ( empty( $transient->checked ) || ! isset( $transient->checked[ $this->plugin_folder ] ) ) {
375
			return false;
376
		}
377
378
		$response = ! isset( $transient->response ) || empty( $transient->response );
379
		if ( $response ) {
380
			return true;
381
		}
382
383
		return isset( $transient->response ) && isset( $transient->response[ $this->plugin_folder ] ) && $transient->checked[ $this->plugin_folder ] === $transient->response[ $this->plugin_folder ]->new_version;
384
	}
385
386
	private function has_been_cleared() {
387
		$last_cleared = get_option( 'frm_last_cleared' );
388
		return ( $last_cleared && $last_cleared > date( 'Y-m-d H:i:s', strtotime( '-5 minutes' ) ) );
389
	}
390
391
	private function cleared_plugins() {
392
		update_option( 'frm_last_cleared', date( 'Y-m-d H:i:s' ) );
393
	}
394
395
	private function is_license_revoked() {
396
		if ( empty( $this->license ) || empty( $this->plugin_slug ) || isset( $_POST['license'] ) ) { // WPCS: CSRF ok.
397
			return;
398
		}
399
400
		if ( is_multisite() ) {
401
			$last_checked = get_site_option( $this->transient_key() );
402
		} else {
403
			$last_checked = get_option( $this->transient_key() );
404
		}
405
406
		$seven_days_ago = date( 'Y-m-d H:i:s', strtotime( '-7 days' ) );
407
408
		if ( ! $last_checked || $last_checked < $seven_days_ago ) {
409
			// check weekly
410
			if ( is_multisite() ) {
411
				update_site_option( $this->transient_key(), date( 'Y-m-d H:i:s' ) );
412
			} else {
413
				update_option( $this->transient_key(), date( 'Y-m-d H:i:s' ) );
414
			}
415
416
			$response = $this->get_license_status();
417
			if ( 'revoked' === $response['status'] || 'blocked' === $response['status'] || 'disabled' === $response['status'] ) {
418
				$this->clear_license();
419
			}
420
		}
421
	}
422
423
	private function transient_key() {
424
		return 'frm_' . md5( sanitize_key( $this->license . '_' . $this->plugin_slug ) );
425
	}
426
427
	public static function activate() {
428
		FrmAppHelper::permission_check( 'frm_change_settings' );
429
		check_ajax_referer( 'frm_ajax', 'nonce' );
430
431
		if ( ! isset( $_POST['license'] ) || empty( $_POST['license'] ) ) {
432
			wp_die(
433
				json_encode(
434
					array(
435
						'message' => __( 'Oops! You forgot to enter your license number.', 'formidable' ),
436
						'success' => false,
437
					)
438
				)
439
			);
440
		}
441
442
		$license = stripslashes( sanitize_text_field( $_POST['license'] ) );
443
		$plugin_slug = sanitize_text_field( $_POST['plugin'] );
444
		$this_plugin = self::get_addon( $plugin_slug );
445
		$response = $this_plugin->activate_license( $license );
446
447
		echo json_encode( $response );
448
		wp_die();
449
	}
450
451
	private function activate_license( $license ) {
452
		$this->set_license( $license );
453
		$this->license = $license;
454
455
		$response = $this->get_license_status();
456
		$response['message'] = '';
457
		$response['success'] = false;
458
459
		if ( $response['error'] ) {
460
			$response['message'] = $response['status'];
461
		} else {
462
			$messages = $this->get_messages();
463
			if ( is_string( $response['status'] ) && isset( $messages[ $response['status'] ] ) ) {
464
				$response['message'] = $messages[ $response['status'] ];
465
			} else {
466
				$response['message'] = FrmAppHelper::kses( $response['status'], array( 'a' ) );
467
			}
468
469
			$is_valid = false;
470
			if ( 'valid' === $response['status'] ) {
471
				$is_valid = 'valid';
472
				$response['success'] = true;
473
			}
474
			$this->set_active( $is_valid );
475
		}
476
477
		return $response;
478
	}
479
480
	private function get_license_status() {
481
		$response = array(
482
			'status' => 'missing',
483
			'error'  => true,
484
		);
485
		if ( empty( $this->license ) ) {
486
			$response['error'] = false;
487
			return $response;
488
		}
489
490
		try {
491
			$response['error'] = false;
492
			$license_data = $this->send_mothership_request( 'activate_license' );
493
494
			// $license_data->license will be either "valid" or "invalid"
495
			if ( is_array( $license_data ) ) {
496
				if ( in_array( $license_data['license'], array( 'valid', 'invalid' ), true ) ) {
497
					$response['status'] = $license_data['license'];
498
				}
499
			} else {
500
				$response['status'] = $license_data;
501
			}
502
		} catch ( Exception $e ) {
503
			$response['status'] = $e->getMessage();
504
		}
505
506
		return $response;
507
	}
508
509
	private function get_messages() {
510
		return array(
511
			'valid'   => __( 'Your license has been activated. Enjoy!', 'formidable' ),
512
			'invalid' => __( 'That license key is invalid', 'formidable' ),
513
			'expired' => __( 'That license is expired', 'formidable' ),
514
			'revoked' => __( 'That license has been refunded', 'formidable' ),
515
			'no_activations_left' => __( 'That license has been used on too many sites', 'formidable' ),
516
			'invalid_item_id' => __( 'Oops! That is the wrong license key for this plugin.', 'formidable' ),
517
			'missing' => __( 'That license key is invalid', 'formidable' ),
518
		);
519
	}
520
521
	public static function deactivate() {
522
		FrmAppHelper::permission_check( 'frm_change_settings' );
523
		check_ajax_referer( 'frm_ajax', 'nonce' );
524
525
		$plugin_slug = sanitize_text_field( $_POST['plugin'] );
526
		$this_plugin = self::get_addon( $plugin_slug );
527
		$license = $this_plugin->get_license();
528
		$this_plugin->license = $license;
529
530
		$response = array(
531
			'success' => false,
532
			'message' => '',
533
		);
534
		try {
535
			// $license_data->license will be either "deactivated" or "failed"
536
			$license_data = $this_plugin->send_mothership_request( 'deactivate_license' );
537
			if ( is_array( $license_data ) && 'deactivated' === $license_data['license'] ) {
538
				$response['success'] = true;
539
				$response['message'] = __( 'That license was removed successfully', 'formidable' );
540
			} else {
541
				$response['message'] = __( 'There was an error deactivating your license.', 'formidable' );
542
			}
543
		} catch ( Exception $e ) {
544
			$response['message'] = $e->getMessage();
545
		}
546
547
		$this_plugin->clear_license();
548
549
		echo json_encode( $response );
550
		wp_die();
551
	}
552
553
	public function send_mothership_request( $action ) {
554
		$api_params = array(
555
			'edd_action' => $action,
556
			'license'    => $this->license,
557
			'url'        => home_url(),
558
		);
559
		if ( is_numeric( $this->download_id ) ) {
560
			$api_params['item_id'] = absint( $this->download_id );
561
		} else {
562
			$api_params['item_name'] = rawurlencode( $this->plugin_name );
563
		}
564
565
		$arg_array = array(
566
			'body'      => $api_params,
567
			'timeout'   => 25,
568
			'user-agent' => $this->plugin_slug . '/' . $this->version . '; ' . get_bloginfo( 'url' ),
569
		);
570
571
		$resp = wp_remote_post( $this->store_url, $arg_array );
572
		$body = wp_remote_retrieve_body( $resp );
573
574
		$message = __( 'Your License Key was invalid', 'formidable' );
575
		if ( is_wp_error( $resp ) ) {
576
			$link = FrmAppHelper::admin_upgrade_link( 'api', 'knowledgebase/why-cant-i-activate-formidable-pro/' );
577
			/* translators: %1$s: Start link HTML, %2$s: End link HTML */
578
			$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>' );
579
			$message .= ' ' . $resp->get_error_message();
580
		} elseif ( 'error' === $body || is_wp_error( $body ) ) {
581
			$message = __( 'You had an HTTP error connecting to the Formidable API', 'formidable' );
582
		} else {
583
			$json_res = json_decode( $body, true );
584
			if ( null !== $json_res ) {
585
				if ( is_array( $json_res ) && isset( $json_res['error'] ) ) {
586
					$message = $json_res['error'];
587
				} else {
588
					$message = $json_res;
589
				}
590
			} elseif ( isset( $resp['response'] ) && isset( $resp['response']['code'] ) ) {
591
				/* translators: %1$s: Error code, %2$s: Error message */
592
				$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...
593
			}
594
		}
595
596
		return $message;
597
	}
598
599
	public function manually_queue_update() {
600
		set_site_transient( 'update_plugins', null );
601
	}
602
}
603