PluginProbe
Patchstack – WordPress & Plugins Security / 2.1.10
Patchstack – WordPress & Plugins Security v2.1.10
2.3.7 trunk 2.1.0 2.1.1 2.1.10 2.1.11 2.1.12 2.1.13 2.1.14 2.1.15 2.1.16 2.1.17 2.1.18 2.1.19 2.1.2 2.1.20 2.1.21 2.1.22 2.1.23 2.1.24 2.1.25 2.1.3 2.1.4 2.1.5 2.1.6 All 49 releases
← All changes | includes/activation.php +349 -858 trunk2.1.10 View file →
@@ -1,858 +1,349 @@
1 -<?php
2 -
3 -// Do not allow the file to be called directly.
4 -if ( ! defined( 'ABSPATH' ) ) {
5 - exit;
6 -}
7 -
8 -/**
9 - * This class is used to activate and deactivate the plugin.
10 - * Additionally, we use it to run migrations.
11 - */
12 -class P_Activation extends P_Core {
13 -
14 - /**
15 - * Holds any activation errors.
16 - *
17 - * @var array
18 - */
19 - private $activation_errors = [];
20 -
21 - /**
22 - * Add the actions required for the activation.
23 - *
24 - * @param Patchstack $core
25 - * @return void
26 - */
27 - public function __construct( $core ) {
28 - parent::__construct( $core );
29 -
30 - add_action( 'activated_plugin', [ $this, 'redirect_activation' ], 10, 2 );
31 - add_action( 'updated_option', [ $this, 'updated_option' ], 10, 3 );
32 - }
33 -
34 - /**
35 - * Redirect the user to our settings page after plugin activation.
36 - *
37 - * @param string $plugin The plugin that is activated.
38 - * @param boolean $network_activation If a network wide activation. (multisite)
39 - * @return void
40 - */
41 - public function redirect_activation( $plugin, $network_activation ) {
42 - if ( defined( 'WP_CLI' ) && WP_CLI ) {
43 - return;
44 - }
45 -
46 - // Only if it's the current plugin.
47 - if ( $plugin == $this->plugin->basename && ! isset( $_REQUEST['_ajax_nonce'] ) ) {
48 -
49 - // If plugin bulk activate through wp-admin, we ignore the redirect if it's more than 1 plugin.
50 - if ( isset( $_POST['checked'] ) && is_array( $_POST['checked'] ) && count( $_POST['checked'] ) > 1 ) {
51 - return;
52 - }
53 -
54 - // If the plugin is already connected or API activated, no need to redirect again.
55 - if ( $this->license_is_active() || $this->is_connected() ) {
56 - return;
57 - }
58 -
59 - // Determine if secret token was set, if so, sync with API.
60 - $attemptAuto = false;
61 - $secretToken = get_option( 'patchstack_activation_secret', '' );
62 - if ( ! empty( $secretToken ) ) {
63 - $attemptAuto = true;
64 - }
65 -
66 - // In case of multisite, we want to redirect the user to a different page.
67 - if ( $network_activation ) {
68 - wp_safe_redirect( network_admin_url( 'admin.php?page=patchstack-multisite-settings&tab=multisite&ps_activated=1' . ($attemptAuto ? '&ps_autoa=1' : '' ) ) );
69 - } else {
70 - wp_safe_redirect( admin_url( 'admin.php?page=' . $this->plugin->name . '&ps_activated=1' . ($attemptAuto ? '&ps_autoa=1' : '' ) ) );
71 - }
72 - exit;
73 - }
74 - }
75 -
76 - /**
77 - * Check if the plugin meets requirements and disable it if they are not present.
78 - *
79 - * @return boolean
80 - */
81 - public function check_requirements() {
82 - if ( $this->meets_requirements() ) {
83 - return true;
84 - }
85 -
86 - // Add a dashboard notice.
87 - add_action( 'all_admin_notices', [ $this, 'requirements_not_met_notice' ] );
88 - return false;
89 - }
90 -
91 - /**
92 - * Check that all plugin requirements are met.
93 - *
94 - * @return boolean
95 - */
96 - public function meets_requirements() {
97 - // Check to see if we can access the API.
98 - $response = wp_remote_request(
99 - $this->plugin->api_url,
100 - [
101 - 'method' => 'GET',
102 - 'timeout' => 10,
103 - 'redirection' => 5,
104 - ]
105 - );
106 -
107 - // Check if we can access the API.
108 - if ( is_wp_error( $response ) ) {
109 - $this->activation_errors[] = 'We were unable to contact our API server. Please contact your host and ask them to make sure that outgoing connections to api.patchstack.com are not blocked.<br />Additional error message to give to your host: ' . $response->get_error_message();
110 - return false;
111 - }
112 -
113 - // Do checks for required classes / functions or similar.
114 - // Add detailed messages to $this->activation_errors array.
115 - if ( version_compare( phpversion(), '5.6.0', '<' ) ) {
116 - $this->activation_errors[] = 'Please update the PHP version on your host to at least 5.6.0. Ask your host if you do not know what this means.';
117 - return false;
118 - }
119 -
120 - global $wp_version;
121 - if ( version_compare( $wp_version, '4.3.0', '<' ) ) {
122 - $this->activation_errors[] = 'Please upgrade your WordPress site to at least 4.3.0.';
123 - return false;
124 - }
125 -
126 - return true;
127 - }
128 -
129 - /**
130 - * Adds a notice to the dashboard if the plugin requirements are not met.
131 - *
132 - * @return void
133 - */
134 - public function requirements_not_met_notice() {
135 - // Deactivate the plugin.
136 - deactivate_plugins( $this->plugin->basename );
137 -
138 - // Compile default message.
139 - $default_message = esc_attr__( 'Patchstack could not be activated due to a conflict. See below for information regarding the conflict.<br />', 'patchstack' );
140 -
141 - // Print the errors on the screen.
142 - echo wp_kses_post( $default_message );
143 - echo wp_kses_post( implode( '<br />', $this->activation_errors ) );
144 - }
145 -
146 - /**
147 - * Activate the plugin.
148 - *
149 - * @param Patchstack $core
150 - * @return void
151 - */
152 - public function activate( $core ) {
153 - // Bail early if requirements are not met.
154 - if ( ! $this->check_requirements() ) {
155 - $this->requirements_not_met_notice();
156 - exit;
157 - }
158 -
159 - // Check if the webarx/webarx.php plugin is present, if so, remove it.
160 - if ( is_dir( WP_PLUGIN_DIR . '/webarx' ) ) {
161 -
162 - // Migrate all current options to the new prefix.
163 - global $wpdb;
164 - $exists = $wpdb->get_var( "SELECT COUNT(*) FROM " . $wpdb->prefix . "options WHERE option_name = 'webarx_api_token'" );
165 -
166 - // Move over the options.
167 - if ( !is_null( $exists ) && $exists >= 1 ) {
168 - $wpdb->query( 'INSERT IGNORE INTO ' . $wpdb->prefix . "options (option_name, option_value, autoload) SELECT REPLACE(option_name, 'webarx_', 'patchstack_') as option_name, option_value, autoload FROM " . $wpdb->prefix . "options WHERE option_name like 'webarx_%'" );
169 - $wpdb->query( 'UPDATE ' . $wpdb->prefix . 'options AS a SET option_value = (SELECT option_value FROM ' . $wpdb->prefix . "options WHERE option_name = REPLACE(a.option_name, 'patchstack_', 'webarx_')) WHERE option_name LIKE 'patchstack_%'" );
170 - }
171 -
172 - // Deactivate the plugin.
173 - include_once ABSPATH . 'wp-admin/includes/plugin.php';
174 - deactivate_plugins( [ 'webarx/webarx.php' ] );
175 - update_option( 'patchstack_license_free', '0', true );
176 - }
177 -
178 - // Make sure any rewrite functionality has been loaded.
179 - $this->migrate();
180 - add_option( 'patchstack_first_activated', '1' );
181 -
182 - // Whether or not we should send a secret key to our API.
183 - $sendSecret = false;
184 -
185 - // Activate the license.
186 - if ( $this->plugin->client_id != 'PATCHSTACK_CLIENT_ID' && $this->plugin->private_key != 'PATCHSTACK_PRIVATE_KEY' ) {
187 - $this->alter_license( $this->plugin->client_id, $this->plugin->private_key, 'activate' );
188 - } elseif ( get_option( 'patchstack_clientid', false ) != false && get_option( 'patchstack_secretkey', false ) != false ) {
189 - $this->alter_license( get_option( 'patchstack_clientid' ), $this->get_secret_key(), 'activate' );
190 - } else {
191 - $sendSecret = true;
192 - update_option( 'patchstack_license_free', '1', true );
193 - }
194 -
195 - // Update firewall status after activating plugin
196 - $api = new P_Api( $core );
197 - $token = $api->get_access_token();
198 - if ( ! empty( $token ) ) {
199 - $api->update_firewall_status( [ 'status' => 1 ] );
200 - $api->update_url( [ 'plugin_url' => get_option( 'siteurl' ) ] );
201 - } elseif ( $sendSecret ) {
202 - $secretToken = wp_generate_password( 36, true );
203 - update_option( 'patchstack_activation_secret', $secretToken );
204 - update_option( 'patchstack_activation_time', time() + 59 ) ;
205 - }
206 -
207 - // Immediately send software data to our server to set firewall as enabled.
208 - // Also immediately download the whitelist file and the firewall rules.
209 - do_action( 'patchstack_send_software_data' );
210 - if ( get_option( 'patchstack_license_free', 0 ) != 1 ) {
211 - do_action( 'patchstack_post_firewall_rules' );
212 - do_action( 'patchstack_post_dynamic_firewall_rules' );
213 - }
214 -
215 - // Try to create the mu-plugins folder/file.
216 - // No need to do this if it already exists.
217 - if ( file_exists( WPMU_PLUGIN_DIR . '/patchstack.php' ) || file_exists( WPMU_PLUGIN_DIR . '/_patchstack.php' ) || ( defined( 'PS_DISABLE_MU' ) && PS_DISABLE_MU ) ) {
218 - return;
219 - }
220 -
221 - // The mu-plugin does not exist, try to create it.
222 - @include_once ABSPATH . 'wp-admin/includes/file.php';
223 - $wpfs = WP_Filesystem();
224 -
225 - // Failed to initialize WP_Filesystem.
226 - if ( ! $wpfs ) {
227 - return;
228 - }
229 -
230 - if ( ! is_dir( WPMU_PLUGIN_DIR ) ) {
231 - wp_mkdir_p( WPMU_PLUGIN_DIR );
232 - }
233 -
234 - // Failed to create the mu-plugin folder.
235 - if ( ! is_dir( WPMU_PLUGIN_DIR ) ) {
236 - return;
237 - }
238 -
239 - // Create the mu-plugin file in the folder.
240 - if ( is_writable( WPMU_PLUGIN_DIR ) ) {
241 - $php = @file_get_contents( trailingslashit( plugin_dir_path( __FILE__ ) ) . 'mu-plugin.php' );
242 - @file_put_contents( trailingslashit( WPMU_PLUGIN_DIR ) . '_patchstack.php', $php );
243 - }
244 - }
245 -
246 - /**
247 - * Build the required Patchstack tables.
248 - *
249 - * @param null|string $ver The version to upgrade to.
250 - * @param null|integer $site_id The blog id to perform the upgrades on.
251 - * @return void
252 - */
253 - public function migrate( $ver = null, $site_id = null ) {
254 - global $wpdb;
255 - require_once ABSPATH . 'wp-admin/includes/upgrade.php';
256 - $charset_collate = $wpdb->get_charset_collate();
257 - $prefix = $site_id != null ? $wpdb->get_blog_prefix( $site_id ) : $wpdb->prefix;
258 -
259 - // The following conditions will only execute if Patchstack is installed because of an update
260 - // and if we need to perform migrations.
261 - if ( $ver !== null && file_exists( dirname( __FILE__ ) . '/migrations/v' . str_replace( '.', '', $ver ) . '.php' ) ) {
262 - require_once dirname( __FILE__ ) . '/migrations/v' . str_replace( '.', '', $ver ) . '.php';
263 - return;
264 - }
265 -
266 - // Require the base migration.
267 - require_once dirname( __FILE__ ) . '/migrations/base.php';
268 - }
269 -
270 - /**
271 - * Check if the database version of the plugin is running behind.
272 - * If so, run the migrations up until the latest version.
273 - *
274 - * @return void
275 - */
276 - public function migrate_check() {
277 - // Only perform migrations if we have any to execute.
278 - $versions = ['3.0.0', '3.0.1', '3.0.2', '3.0.3', '3.0.4'];
279 - if ( count( $versions ) == 0 ) {
280 - return;
281 - }
282 -
283 - // Get current database version and run the migrations.
284 - $db_version = get_option( 'patchstack_db_version', false );
285 - foreach ( $versions as $version ) {
286 - if ( version_compare( $db_version, $version, '<' ) ) {
287 - $this->migrate( $version );
288 - }
289 - }
290 - }
291 -
292 - /**
293 - * Perform cleanup when the plugin is deactivated.
294 - *
295 - * @return void
296 - */
297 - public function deactivate() {
298 - // Update firewall status after de-activating plugin
299 - try {
300 - $token = $this->plugin->api->get_access_token();
301 - if ( ! empty( $token ) ) {
302 - $this->plugin->api->update_firewall_status( [ 'status' => 0 ] );
303 - }
304 - } catch (\Exception $e) {
305 - //
306 - }
307 -
308 - // Clear all Patchstack scheduled tasks.
309 - $tasks = [ 'patchstack_zip_backup', 'patchstack_send_software_data', 'patchstack_send_hacker_logs', 'patchstack_send_visitor_logs', 'patchstack_send_event_logs', 'patchstack_reset_blocked_attacks', 'patchstack_post_firewall_rules', 'patchstack_post_firewall_htaccess_rules', 'patchstack_post_dynamic_firewall_rules', 'patchstack_update_license_status', 'patchstack_update_plugins', 'patchstack_send_ping', 'patchstack_check_env', 'puc_cron_check_updates-webarx' ];
310 - foreach ( $tasks as $task ) {
311 - wp_clear_scheduled_hook( $task );
312 - }
313 -
314 - // Cleanup the .htaccess file.
315 - $this->plugin->htaccess->cleanup_htaccess_file();
316 - $this->auto_prepend_removal();
317 -
318 - // Remove the mu-plugin file if it exists.
319 - foreach (['patchstack.php', '_patchstack.php'] as $file) {
320 - if ( file_exists( WPMU_PLUGIN_DIR . '/' . $file )) {
321 - wp_delete_file( WPMU_PLUGIN_DIR . '/' . $file );
322 - }
323 - }
324 - }
325 -
326 - /**
327 - * Activate or deactivate a license on the current site.
328 - *
329 - * @param integer $id
330 - * @param string $secret
331 - * @param string $action
332 - * @return array
333 - */
334 - public function alter_license( $id, $secret, $action ) {
335 - // Set default options in case they have not been set yet.
336 - $this->plugin->admin_options->settings_init();
337 -
338 - // Store current keys in tmp variable so in case it fails, we can set it back.
339 - $tmp_id = get_option( 'patchstack_clientid' );
340 - $tmp_key = $this->get_secret_key();
341 -
342 - // Set the new values.
343 - update_option( 'patchstack_clientid', $id );
344 - $this->set_secret_key( $secret );
345 -
346 - // Activate the license.
347 - if ( $action == 'activate' ) {
348 - $api_result = $this->plugin->api->get_access_token( $id, $secret, true );
349 -
350 - // Valid result?
351 - if ( ! $api_result ) {
352 - update_option( 'patchstack_clientid', $tmp_id );
353 - $this->set_secret_key( $tmp_key );
354 -
355 - return [
356 - 'result' => 'error',
357 - 'body' => json_encode($this->plugin->api->message),
358 - 'message' => 'Cannot activate license!',
359 - ];
360 - }
361 -
362 - // Successfully activated.
363 - update_option( 'patchstack_license_activated', '1', true );
364 -
365 - // Update license status and fetch policy settings.
366 - $fetchPolicy = (int) get_option( 'patchstack_last_license_check', 0 ) == 0;
367 - $this->plugin->api->update_license_status( $fetchPolicy );
368 -
369 - // Perform post-activation actions, incl. access token retrieval.
370 - $token = $this->plugin->api->get_access_token();
371 - if ( ! empty( $token ) ) {
372 -
373 - // Immediately send software data to our server to set firewall as enabled.
374 - // Also immediately download the whitelist file and the firewall rules.
375 - do_action( 'patchstack_send_software_data' );
376 - if ( get_option( 'patchstack_license_free', 0 ) != 1 ) {
377 - update_option( 'patchstack_basic_firewall', 1, true );
378 - do_action( 'patchstack_post_firewall_rules' );
379 - do_action( 'patchstack_post_dynamic_firewall_rules' );
380 - $this->header();
381 - }
382 -
383 - // Update firewall status, URL and ping API.
384 - $this->plugin->api->update_firewall_status( [ 'status' => $this->get_option( 'patchstack_basic_firewall' ) == 1 ] );
385 - $this->plugin->api->update_url( [ 'plugin_url' => get_option( 'siteurl' ) ] );
386 - $this->auto_prepend_injection();
387 - }
388 -
389 - return [
390 - 'result' => 'success',
391 - 'message' => 'License activated!',
392 - ];
393 - }
394 -
395 - // Deactivate the license.
396 - if ( $action == 'deactivate' ) {
397 - update_option( 'patchstack_api_token', '' );
398 - update_option( 'patchstack_license_activated', '0' );
399 - update_option( 'patchstack_clientid', '' );
400 - update_option( 'patchstack_secretkey', '' );
401 - update_option( 'patchstack_secretkey_nonce', '' );
402 -
403 - $this->auto_prepend_removal();
404 -
405 - return [
406 - 'result' => 'success',
407 - 'message' => 'License deactivated!',
408 - ];
409 - }
410 - }
411 -
412 - /**
413 - * Send a request to our API for the IP address header.
414 - *
415 - * @return void
416 - */
417 - public function header()
418 - {
419 - $header = get_option( 'patchstack_firewall_ip_header', '' );
420 - $computed = get_option( 'patchstack_ip_header_computed', 0 );
421 - $force = get_option( 'patchstack_ip_header_force_compute', 0 );
422 -
423 - if ( ( $header == '' && ! $computed ) || $force ) {
424 - // Create an OTT token.
425 - $ott = md5( wp_generate_password( 32, true, true ) );
426 - update_option( 'patchstack_ott_action', $ott );
427 -
428 - // Tell our API.
429 - wp_remote_request(
430 - $this->plugin->api_url . '/api/header',
431 - [
432 - 'method' => 'POST',
433 - 'timeout' => 60,
434 - 'redirection' => 5,
435 - 'httpversion' => '1.0',
436 - 'blocking' => true,
437 - 'headers' => [
438 - 'Source-Host' => get_site_url(),
439 - ],
440 - 'body' => [
441 - 'token' => $ott,
442 - 'url' => get_site_url()
443 - ],
444 - 'cookies' => [],
445 - ]
446 - );
447 - }
448 - }
449 -
450 - /**
451 - * Create the environment needed for the auto prepend firewall functionality.
452 - * 1. First we check if an auto_prepend_file already exists somewhere.
453 - * 2. Then we write to the .htaccess file and check its status code.
454 - * 3. Then we write to the .user.ini file and check its status code, .user.ini is optional if there are any errors with it.
455 - *
456 - * @param boolean $refresh
457 - * @return boolean
458 - */
459 - public function auto_prepend_injection($refresh = false)
460 - {
461 - // Determine if AP firewall is enabled.
462 - if ( ! get_option( 'patchstack_firewall_ap_enabled', false ) ) {
463 - return;
464 - }
465 -
466 - // Determine if we received an error that hasn't been cleared yet.
467 - if ( get_option( 'patchstack_firewall_ap_error', '' ) != '' ) {
468 - return;
469 - }
470 -
471 - // No need to display this error if the .htaccess functionality has been disabled.
472 - if ( get_option( 'patchstack_disable_htaccess', 0 ) || ( defined( 'PS_DISABLE_HTACCESS' ) && PS_DISABLE_HTACCESS ) || ( defined( 'PS_DISABLE_MU' ) && PS_DISABLE_MU ) ) {
473 - return;
474 - }
475 -
476 - // Get filesystem.
477 - global $wp_filesystem;
478 - if ( ! $this->get_filesystem() ) {
479 - update_option( 'patchstack_firewall_ap_error', 'Could not establish filesystem.' );
480 - return false;
481 - }
482 -
483 - // First ensure a .htaccess file exists, otherwise no point.
484 - $htaccess_file = ABSPATH . '.htaccess';
485 - if ( ! $wp_filesystem->exists( $htaccess_file ) && ! $wp_filesystem->touch( $htaccess_file ) ) {
486 - update_option( 'patchstack_firewall_ap_error', 'The .htaccess file could be found nor created.' );
487 - return false;
488 - }
489 -
490 - // Completely halt if there is already an auto_prepend_file present in .htaccess and not of Patchstack.
491 - $htaccess_content = $wp_filesystem->get_contents( $htaccess_file );
492 - if ( stripos( $htaccess_content, 'auto_prepend_file' ) !== false && stripos( $htaccess_content, 'mu-plugin-ap.php' ) === false ) {
493 - update_option( 'patchstack_firewall_ap_error', 'A different auto_prepend_file value is already present in the .htaccess file.' );
494 - return false;
495 - }
496 -
497 - // Completely halt if there is already an auto_prepend_file present in .user.ini and not of Patchstack.
498 - $user_ini = ini_get( 'user_ini.filename' );
499 - if ( $user_ini && $wp_filesystem->exists( ABSPATH . $user_ini ) ) {
500 - $ini_content = $wp_filesystem->get_contents( ABSPATH . $user_ini );
501 - if ( stripos( $ini_content, 'auto_prepend_file' ) !== false && stripos( $ini_content, 'mu-plugin-ap.php' ) === false ) {
502 - update_option( 'patchstack_firewall_ap_error', 'A different auto_prepend_file value is already present in the .user.ini file.' );
503 - return false;
504 - }
505 - }
506 -
507 - // Determine if we can write the /wp-content/pslogs/ folder.
508 - $logs_dir = WP_CONTENT_DIR . '/pslogs/';
509 - if ( ! $wp_filesystem->exists( $logs_dir ) && ! $wp_filesystem->mkdir( $logs_dir ) ) {
510 - update_option( 'patchstack_firewall_ap_error', 'The path ' . $logs_dir . ' could not be created.' );
511 - return false;
512 - }
513 -
514 - // Create the blank index.php file.
515 - if ( ! $wp_filesystem->exists( $logs_dir . 'index.php' ) && ! $wp_filesystem->put_contents( $logs_dir . 'index.php', '' ) ) {
516 - update_option( 'patchstack_firewall_ap_error', 'The file ' . $logs_dir . 'index.php could not be created.' );
517 - return false;
518 - }
519 -
520 - // Create the logs.php file.
521 - if ( ! $wp_filesystem->exists( $logs_dir . 'logs.php' ) && ! $wp_filesystem->put_contents( $logs_dir . 'logs.php', '<?php exit; ?>' . PHP_EOL ) ) {
522 - update_option( 'patchstack_firewall_ap_error', 'The file ' . $logs_dir . 'logs.php could not be created.' );
523 - return false;
524 - }
525 -
526 - // Save current site id.
527 - $current_id = get_current_blog_id();
528 -
529 - // Pull data to save into the config.php file.
530 - $sites = $this->get_sites();
531 - $data = [];
532 - foreach ($sites as $site) {
533 - $this->switch_to_blog( $site->id );
534 - $data[] = [
535 - 'site_id' => $site->id,
536 - 'site_url' => preg_replace( '/^https?:\/\//i', '', $site->siteurl ),
537 - 'home_url' => preg_replace( '/^https?:\/\//i', '', get_option( 'home' ) ),
538 - 'patchstack_basic_firewall' => get_option( 'patchstack_basic_firewall', 1 ),
539 - 'patchstack_license_activated' => get_option( 'patchstack_license_activated', 0 ),
540 - 'patchstack_license_free' => get_option( 'patchstack_license_free', 0 ),
541 - 'patchstack_firewall_ip_header' => get_option( 'patchstack_firewall_ip_header', '' ),
542 - 'patchstack_firewall_rules_v3_ap' => base64_encode( get_option( 'patchstack_firewall_rules_v3_ap', '[]' ) )
543 - ];
544 - }
545 -
546 - // Switch back to current site.
547 - $this->switch_to_blog( $current_id );
548 -
549 - // Save into the config.php file.
550 - if ( ! $wp_filesystem->put_contents( $logs_dir . 'config.php', '<?php return ' . var_export( $data, true ) . ';' ) ) {
551 - update_option( 'patchstack_firewall_ap_error', 'The file ' . $logs_dir . 'config.php could not be created.' );
552 - return false;
553 - }
554 -
555 - // In case we only want to refresh the auto prepend rules, we stop here.
556 - if ( $refresh ) {
557 - return true;
558 - }
559 -
560 - // Prepare the rules to inject into .htaccess.
561 - $prepend_rules = $this->get_auto_prepend_rules();
562 - if ( ! $prepend_rules ) {
563 - return false;
564 - }
565 -
566 - // Determine if the rules already exist and overwrite them in case of path change.
567 - $original_htaccess = $htaccess_content;
568 - $re = '/# BEGIN AP Patchstack.*?# END AP Patchstack/is';
569 - if ( preg_match( $re, $htaccess_content ) ) {
570 - $htaccess_content = preg_replace( $re, rtrim($prepend_rules['htaccess']), $htaccess_content );
571 - } else {
572 - $htaccess_content .= "\n" . $prepend_rules['htaccess'];
573 - }
574 -
575 - // Attempt to write to the .htaccess file.
576 - if ( ! $wp_filesystem->put_contents( $htaccess_file, $htaccess_content ) ) {
577 - update_option( 'patchstack_firewall_ap_error', 'Could not inject into the .htaccess file.' );
578 - return false;
579 - }
580 -
581 - // Determine if the site still works as expected with the injected htaccess rules.
582 - if ( $this->get_site_status_code() >= 400 ) {
583 - $wp_filesystem->put_contents( $htaccess_file, $original_htaccess );
584 - update_option( 'patchstack_firewall_ap_error', 'The .htaccess rules caused a fatal internal server error.' );
585 - return false;
586 - }
587 -
588 - // Ensure a .user.ini is present.
589 - $user_ini = ini_get( 'user_ini.filename' );
590 - if ( ! $user_ini ) {
591 - update_option( 'patchstack_firewall_ap_error', '' );
592 - return true;
593 - }
594 -
595 - // Define full path to the .user.ini file.
596 - $user_ini = ABSPATH . $user_ini;
597 -
598 - // Create the file if it does not exist.
599 - if ( ! $wp_filesystem->exists( $user_ini ) && ! $wp_filesystem->touch( $user_ini ) ) {
600 - update_option( 'patchstack_firewall_ap_error', 'The .user.ini file could not be created.' );
601 - return true;
602 - }
603 -
604 - // Get the contents of the current .user.ini file.
605 - $ini_content = $wp_filesystem->get_contents( $user_ini );
606 -
607 - // Determine if the rules already exist and overwrite them in case of path change.
608 - $original_ini = $ini_content;
609 - $re = '/; BEGIN AP Patchstack.*?; END AP Patchstack/is';
610 - if ( preg_match( $re, $ini_content ) ) {
611 - $ini_content = preg_replace( $re, rtrim($prepend_rules['ini']), $ini_content );
612 - } else {
613 - $ini_content .= "\n" . $prepend_rules['ini'];
614 - }
615 -
616 - // Attempt to write to the .user.ini file.
617 - if ( ! $wp_filesystem->put_contents( $user_ini, $ini_content ) ) {
618 - update_option( 'patchstack_firewall_ap_error', 'Could not inject into the .user.ini file.' );
619 - return true;
620 - }
621 -
622 - // Determine if the site still works as expected with the injected .user.ini rules.
623 - if ( $this->get_site_status_code() == 500 ) {
624 - $wp_filesystem->put_contents( $user_ini, $original_ini );
625 - update_option( 'patchstack_firewall_ap_error', 'The .user.ini rules caused a fatal internal server error.' );
626 - return false;
627 - }
628 -
629 - update_option( 'patchstack_firewall_ap_error', '' );
630 - return true;
631 - }
632 -
633 - /**
634 - * Remove everything related to the auto prepend functionality.
635 - *
636 - * @return boolean
637 - */
638 - public function auto_prepend_removal()
639 - {
640 - global $wp_filesystem;
641 - $this->get_filesystem();
642 -
643 - // Define our paths to access.
644 - $logs_dir = WP_CONTENT_DIR . '/pslogs/';
645 - $htaccess_file = ABSPATH . '.htaccess';
646 - $ini_file = ABSPATH . '.user.ini';
647 -
648 - // Remove the entire /pslogs/ directory.
649 - if ( $wp_filesystem->is_dir( $logs_dir ) ) {
650 - $wp_filesystem->delete( $logs_dir, true );
651 - }
652 -
653 - // Remove the .htaccess injected rules.
654 - if ( $wp_filesystem->is_file( $htaccess_file ) ) {
655 - $htaccess_content = $wp_filesystem->get_contents( $htaccess_file );
656 - $re = '/# BEGIN AP Patchstack.*?# END AP Patchstack/is';
657 - if ( preg_match( $re, $htaccess_content ) ) {
658 - $htaccess_content = preg_replace( $re, '', $htaccess_content );
659 - $wp_filesystem->put_contents( $htaccess_file, $htaccess_content );
660 - }
661 - }
662 -
663 - // Remove the .user.ini injected rules.
664 - if ( $wp_filesystem->is_file( $ini_file ) ) {
665 - $ini_content = $wp_filesystem->get_contents( $ini_file );
666 - $re = '/; BEGIN AP Patchstack.*?; END AP Patchstack/is';
667 - if ( preg_match( $re, $ini_content ) ) {
668 - $ini_content = preg_replace( $re, '', $ini_content );
669 - $wp_filesystem->put_contents( $ini_file, $ini_content );
670 - }
671 - }
672 - }
673 -
674 - /**
675 - * Attempt to establish the proper WP_FileSystem.
676 - *
677 - * @return boolean
678 - */
679 - private function get_filesystem()
680 - {
681 - // Seems to be the only native way to obtain FTP credentials, if defined.
682 - include_once( ABSPATH . 'wp-admin/includes/file.php' );
683 - ob_start();
684 - $creds = request_filesystem_credentials( admin_url( 'admin-ajax.php' ), '', false, ABSPATH, null, true );
685 - ob_end_clean();
686 -
687 - // Returns false if no filesystem connection could be determined.
688 - if ( $creds === false ) {
689 - update_option( 'patchstack_firewall_ap_error', 'Unable to establish filesystem connection.' );
690 - return false;
691 - }
692 -
693 - // Attempt to initialize it.
694 - $fs = WP_Filesystem( $creds, ABSPATH, true );
695 - if ( ! $fs ) {
696 - update_option( 'patchstack_firewall_ap_error', 'Unable to establish filesystem connection through acquired creds.' );
697 - return false;
698 - }
699 -
700 - return true;
701 - }
702 -
703 - /**
704 - * Get sites as part of the environment.
705 - *
706 - * @return array
707 - */
708 - private function get_sites()
709 - {
710 - if ( ! function_exists( 'get_sites' ) ) {
711 - return [
712 - (object) [
713 - 'id' => 0,
714 - 'siteurl' => get_site_url()
715 - ]
716 - ];
717 - }
718 -
719 - return get_sites();
720 - }
721 -
722 - /**
723 - * Switch to a different site.
724 - *
725 - * @param integer $site_id
726 - * @return void
727 - */
728 - private function switch_to_blog($site_id)
729 - {
730 - if ( ! function_exists( 'switch_to_blog' ) ) {
731 - return;
732 - }
733 -
734 - switch_to_blog( $site_id );
735 - }
736 -
737 - /**
738 - * Determine the web-server software and make sure we support it before we generate the .htaccess rules for it.
739 - *
740 - * @return array|boolean
741 - */
742 - private function get_auto_prepend_rules()
743 - {
744 - // Establish location of the auto prepend file.
745 - $mu_file = __DIR__ . '/mu-plugin-ap.php';
746 - if ( ! file_exists( $mu_file ) ) {
747 - return false;
748 - }
749 -
750 - // Ensure that the SERVER_SOFTWARE value is set.
751 - $software = isset( $_SERVER['SERVER_SOFTWARE'] ) ? $_SERVER['SERVER_SOFTWARE'] : '';
752 - if ( ! $software ) {
753 - update_option( 'patchstack_firewall_ap_error', 'Unsupported SERVER_SOFTWARE, found: ' . $software );
754 - return false;
755 - }
756 -
757 - // At this time, reject non-Apache environments.
758 - $sapi = function_exists( 'php_sapi_name' ) ? php_sapi_name() : false;
759 - if ( ! $sapi || stripos( $_SERVER['SERVER_SOFTWARE'], 'litespeed' ) === false && $sapi != 'litespeed' && stripos($_SERVER['SERVER_SOFTWARE'], 'apache' ) === false) {
760 - update_option( 'patchstack_firewall_ap_error', 'Unsupported SERVER_SOFTWARE, found: ' . $_SERVER['SERVER_SOFTWARE'] . ' and ' . $sapi );
761 - return false;
762 - }
763 -
764 - // Seperate flag for LiteSpeed.
765 - $is_litespeed = stripos( $_SERVER['SERVER_SOFTWARE'], 'litespeed' ) !== false || $sapi == 'litespeed';
766 -
767 - // Attempt to find the Apache version, < 2.4 does not support <If>.
768 - // This depends on ServerTokens value, so only stop execution if we can't find the specific unsupported versions.
769 - $version = function_exists( 'apache_get_version' ) ? apache_get_version() : $software;
770 - if ( ! $is_litespeed && stripos( $version, 'Apache/2.4' ) === false ) {
771 - update_option( 'patchstack_firewall_ap_error', 'Unsupported SERVER_SOFTWARE, found: ' . $software );
772 - return false;
773 - }
774 -
775 - // Add c-style slashes.
776 - $mu_file_as = wp_normalize_path(addcslashes($mu_file, "'"));
777 -
778 - // Bit different rules for LiteSpeed.
779 - if ( ! $is_litespeed ) {
780 - $rules = "<IfModule mod_php.c>
781 - php_value auto_prepend_file '" . $mu_file_as . "'
782 - </IfModule>
783 - <IfModule mod_php5.c>
784 - php_value auto_prepend_file '" . $mu_file_as . "'
785 - </IfModule>
786 - <IfModule mod_php7.c>
787 - php_value auto_prepend_file '" . $mu_file_as . "'
788 - </IfModule>";
789 - } else {
790 - $rules = "<IfModule LiteSpeed>
791 - php_value auto_prepend_file '" . $mu_file_as . "'
792 - </IfModule>
793 - <IfModule lsapi_module>
794 - php_value auto_prepend_file '" . $mu_file_as . "'
795 - </IfModule>";
796 - }
797 -
798 - return [
799 - 'htaccess' => "# BEGIN AP Patchstack
800 -<IfModule mod_authz_core.c>
801 - <If \"-f '" . $mu_file_as . "'\">
802 - " . $rules . "
803 -
804 - <Files \".user.ini\">
805 - <IfModule mod_authz_core.c>
806 - Require all denied
807 - </IfModule>
808 - <IfModule !mod_authz_core.c>
809 - Order deny,allow
810 - Deny from all
811 - </IfModule>
812 - </Files>
813 - </If>
814 -</IfModule>
815 -# END AP Patchstack
816 -",
817 - 'ini' => "; BEGIN AP Patchstack
818 -auto_prepend_file = '" . $mu_file_as . "'
819 -; END AP Patchstack
820 -"
821 - ];
822 - }
823 -
824 - /**
825 - * Retrieve the status code of the site.
826 - * This is done to determine if the .htaccess rules do not trigger an error.
827 - *
828 - * @return integer
829 - */
830 - public function get_site_status_code() {
831 - $response = wp_remote_get( get_site_url() );
832 - $http_code = wp_remote_retrieve_response_code( $response );
833 - return $http_code;
834 - }
835 -
836 - /**
837 - * If option is updated, refresh AP config file.
838 - *
839 - * @param string $option_name
840 - * @param string $option_name
841 - * @param mixed $value
842 - * @return void
843 - */
844 - public function updated_option( $option_name, $old_value, $value ) {
845 - // Only allow to run for our options. The IP header is embedded in the AP config
846 - // file too, so a change there must also regenerate it.
847 - if ( !in_array( $option_name, [ 'patchstack_basic_firewall', 'patchstack_license_free', 'patchstack_firewall_rules_v3_ap', 'patchstack_firewall_ip_header' ] ) ) {
848 - return;
849 - }
850 -
851 - // Not strict type matching.
852 - if ( $old_value == $value ) {
853 - return;
854 - }
855 -
856 - $this->auto_prepend_injection(true);
857 - }
858 -}
1 +<?php
2 +
3 +// Do not allow the file to be called directly.
4 +if ( ! defined( 'ABSPATH' ) ) {
5 + exit;
6 +}
7 +
8 +/**
9 + * This class is used to activate and deactivate the plugin.
10 + * Additionally, we use it to run migrations.
11 + */
12 +class P_Activation extends P_Core {
13 +
14 + /**
15 + * Add the actions required for the activation.
16 + *
17 + * @param Patchstack $core
18 + * @return void
19 + */
20 + public function __construct( $core ) {
21 + parent::__construct( $core );
22 + add_action( 'activated_plugin', array( $this, 'redirect_activation' ), 10, 2 );
23 + }
24 +
25 + /**
26 + * Redirect the user to our settings page after plugin activation.
27 + *
28 + * @param string $plugin The plugin that is activated.
29 + * @param boolean $network_activation If a network wide activation. (multisite)
30 + * @return void
31 + */
32 + public function redirect_activation( $plugin, $network_activation ) {
33 + if ( $plugin == $this->plugin->basename ) {
34 +
35 + // In case of multisite, we want to redirect the user to a different page.
36 + if ( $network_activation ) {
37 + wp_safe_redirect( network_admin_url( 'admin.php?page=patchstack-multisite-settings&tab=multisite&activated=1' ) );
38 + } else {
39 + wp_safe_redirect( admin_url( 'admin.php?page=' . $this->plugin->name . '&activated=1' ) );
40 + }
41 + exit;
42 + }
43 + }
44 +
45 + /**
46 + * Check if the plugin meets requirements and disable it if they are not present.
47 + *
48 + * @return boolean
49 + */
50 + public function check_requirements() {
51 + if ( $this->meets_requirements() ) {
52 + return true;
53 + }
54 +
55 + // Add a dashboard notice.
56 + add_action( 'all_admin_notices', array( $this, 'requirements_not_met_notice' ) );
57 + return false;
58 + }
59 +
60 + /**
61 + * Check that all plugin requirements are met.
62 + *
63 + * @return boolean
64 + */
65 + public function meets_requirements() {
66 + // Check to see if we can access the API.
67 + $response = wp_remote_request(
68 + $this->plugin->api_url,
69 + array(
70 + 'method' => 'GET',
71 + 'timeout' => 10,
72 + 'redirection' => 5,
73 + )
74 + );
75 +
76 + // Check if we can access the API.
77 + if ( is_wp_error( $response ) ) {
78 + $this->activation_errors[] = 'We were unable to contact our API server. Please contact your host and ask them to make sure that outgoing connections to api.webarxsecurity.com and api.patchstack.com are not blocked.<br />Additional error message to give to your host: ' . $response->get_error_message();
79 + return false;
80 + }
81 +
82 + // Do checks for required classes / functions or similar.
83 + // Add detailed messages to $this->activation_errors array.
84 + if ( version_compare( phpversion(), '5.3.0', '<' ) ) {
85 + $this->activation_errors[] = 'Please update the PHP version on your host to at least 5.3.0. Ask your host if you do not know what this means.';
86 + return false;
87 + }
88 +
89 + global $wp_version;
90 + if ( version_compare( $wp_version, '4.3.0', '<' ) ) {
91 + $this->activation_errors[] = 'Please upgrade your WordPress site to at least 4.3.0.';
92 + return false;
93 + }
94 +
95 + return true;
96 + }
97 +
98 + /**
99 + * Adds a notice to the dashboard if the plugin requirements are not met.
100 + *
101 + * @return void
102 + */
103 + public function requirements_not_met_notice() {
104 + // Deactivate the plugin.
105 + deactivate_plugins( $this->plugin->basename );
106 +
107 + // Compile default message.
108 + $default_message = __( 'Patchstack could not be activated due to a conflict. See below for information regarding the conflict.<br />', 'patchstack' );
109 +
110 + // Print the errors on the screen.
111 + echo wp_kses_post( $default_message );
112 + echo wp_kses_post( implode( '<br />', $this->activation_errors ) );
113 + }
114 +
115 + /**
116 + * Activate the plugin.
117 + *
118 + * @param Patchstack $core
119 + * @return void
120 + */
121 + public function activate( $core ) {
122 + // Bail early if requirements are not met.
123 + if ( ! $this->check_requirements() ) {
124 + $this->requirements_not_met_notice();
125 + exit;
126 + }
127 +
128 + // Check if the webarx/webarx.php plugin is present, if so, remove it.
129 + if ( is_dir( WP_PLUGIN_DIR . '/webarx' ) ) {
130 +
131 + // Migrate all current options to the new prefix.
132 + global $wpdb;
133 + $exists = $wpdb->get_var( "SELECT COUNT(*) FROM " . $wpdb->prefix . "options WHERE option_name = 'webarx_api_token'" );
134 +
135 + // Move over the options.
136 + if ( !is_null( $exists ) && $exists >= 1 ) {
137 + $wpdb->query( 'INSERT IGNORE INTO ' . $wpdb->prefix . "options (option_name, option_value, autoload) SELECT REPLACE(option_name, 'webarx_', 'patchstack_') as option_name, option_value, autoload FROM " . $wpdb->prefix . "options WHERE option_name like 'webarx_%'" );
138 + $wpdb->query( 'UPDATE ' . $wpdb->prefix . 'options AS a SET option_value = (SELECT option_value FROM ' . $wpdb->prefix . "options WHERE option_name = REPLACE(a.option_name, 'patchstack_', 'webarx_')) WHERE option_name LIKE 'patchstack_%'" );
139 + }
140 +
141 + // Deactivate the plugin.
142 + include_once ABSPATH . 'wp-admin/includes/plugin.php';
143 + deactivate_plugins( array( 'webarx/webarx.php' ) );
144 + update_option( 'patchstack_license_free', '0' );
145 + }
146 +
147 + // Make sure any rewrite functionality has been loaded.
148 + $this->migrate();
149 + add_option( 'patchstack_first_activated', '1' );
150 +
151 + // Activate the license.
152 + if ( $this->plugin->client_id != 'PATCHSTACK_CLIENT_ID' && $this->plugin->private_key != 'PATCHSTACK_PRIVATE_KEY' ) {
153 + $this->alter_license( $this->plugin->client_id, $this->plugin->private_key, 'activate' );
154 + } elseif ( get_option( 'patchstack_clientid', false ) != false && get_option( 'patchstack_secretkey', false ) != false ) {
155 + $this->alter_license( get_option( 'patchstack_clientid' ), get_option( 'patchstack_secretkey' ), 'activate' );
156 + } else {
157 + update_option( 'patchstack_license_free', '1' );
158 + }
159 +
160 + // Update firewall status after activating plugin
161 + $api = new P_Api( $core );
162 + $token = $api->get_access_token();
163 + if ( ! empty( $token ) ) {
164 + $api->update_firewall_status( array( 'status' => 1 ) );
165 + $api->update_url( array( 'plugin_url' => get_option( 'siteurl' ) ) );
166 + }
167 +
168 + // Immediately send software data to our server to set firewall as enabled.
169 + // Also immediately download the whitelist file and the firewall rules.
170 + do_action( 'patchstack_send_software_data' );
171 + if ( get_option( 'patchstack_license_free', 0 ) != 1 ) {
172 + do_action( 'patchstack_post_firewall_rules' );
173 + do_action( 'patchstack_post_dynamic_firewall_rules' );
174 + }
175 +
176 + // One time actions should be placed here.
177 + $this->plugin->hardening->delete_readme();
178 + }
179 +
180 + /**
181 + * Used to activate an individual license on multisite/network.
182 + *
183 + * @param object $site
184 + * @param array $license
185 + * @return void
186 + */
187 + public function activate_multisite_license( $site, $license ) {
188 + // Build the Patchstack tables on the site.
189 + $this->migrate( null, $site->id );
190 +
191 + // Add the options to given site.
192 + foreach ( $this->plugin->admin_options->options as $name => $value ) {
193 + add_blog_option( $site->id, $name, $value );
194 + }
195 +
196 + // Set the client id and secret key.
197 + update_blog_option( $site->id, 'patchstack_clientid', $license['id'] );
198 + update_blog_option( $site->id, 'patchstack_secretkey', $license['secret'] );
199 + $this->plugin->api->blog_id = $site->id;
200 +
201 + // Activate the license and update firewall status after activating the plugin.
202 + $token = $this->plugin->api->get_access_token( $license['id'], $license['secret'], true );
203 + if ( ! empty( $token ) ) {
204 + $this->plugin->api->update_firewall_status( array( 'status' => $this->get_option( 'patchstack_basic_firewall' ) == 1 ) );
205 + $this->plugin->api->update_url( array( 'plugin_url' => get_blog_option( $site->id, 'siteurl' ) ) );
206 +
207 + // If we have an access token, tell our API that the firewall is activated
208 + // and the current URL of the site.
209 + update_blog_option( $site->id, 'patchstack_license_activated', '1' );
210 + $this->plugin->api->update_license_status();
211 +
212 + // This will trigger the software synchronization action.
213 + wp_remote_get( get_site_url( $site->id ), array( 'sslverify' => false ) );
214 + }
215 +
216 + // Make sure to switch back to the current blog id.
217 + $this->plugin->api->blog_id = get_current_blog_id();
218 + }
219 +
220 + /**
221 + * Build the required Patchstack tables.
222 + *
223 + * @param null|string $ver The version to upgrade to.
224 + * @param null|integer $site_id The blog id to perform the upgrades on.
225 + * @return void
226 + */
227 + public function migrate( $ver = null, $site_id = null ) {
228 + global $wpdb;
229 + require_once ABSPATH . 'wp-admin/includes/upgrade.php';
230 + $charset_collate = $wpdb->get_charset_collate();
231 + $prefix = $site_id != null ? $wpdb->get_blog_prefix( $site_id ) : $wpdb->prefix;
232 +
233 + // The following conditions will only execute if Patchstack is installed because of an update
234 + // and if we need to perform migrations.
235 + if ( $ver !== null && file_exists( dirname( __FILE__ ) . '/migrations/v' . str_replace( '.', '', $ver ) . '.php' ) ) {
236 + require_once dirname( __FILE__ ) . '/migrations/v' . str_replace( '.', '', $ver ) . '.php';
237 + return;
238 + }
239 +
240 + // Require the base migration.
241 + require_once dirname( __FILE__ ) . '/migrations/base.php';
242 + }
243 +
244 + /**
245 + * Check if the database version of the plugin is running behind.
246 + * If so, run the migrations up until the latest version.
247 + *
248 + * @return void
249 + */
250 + public function migrate_check() {
251 + // Only perform migrations if we have any to execute.
252 + $versions = array('3.0.0');
253 + if ( count( $versions ) == 0 ) {
254 + return;
255 + }
256 +
257 + // Get current database version and run the migrations.
258 + $db_version = get_option( 'patchstack_db_version', false );
259 + foreach ( $versions as $version ) {
260 + if ( version_compare( $db_version, $version, '<' ) ) {
261 + $this->migrate( $version );
262 + }
263 + }
264 + }
265 +
266 + /**
267 + * Perform cleanup when the plugin is deactivated.
268 + *
269 + * @return void
270 + */
271 + public function deactivate() {
272 + // Update firewall status after de-activating plugin
273 + $api = new P_Api( $this );
274 + $token = $api->get_access_token();
275 + if ( ! empty( $token ) ) {
276 + $api->update_firewall_status( array( 'status' => 0 ) );
277 + }
278 +
279 + // Clear all Patchstack scheduled tasks.
280 + $tasks = array( 'patchstack_zip_backup', 'patchstack_send_software_data', 'patchstack_send_hacker_logs', 'patchstack_send_visitor_logs', 'patchstack_send_event_logs', 'patchstack_reset_blocked_attacks', 'patchstack_post_firewall_rules', 'patchstack_post_firewall_htaccess_rules', 'patchstack_post_dynamic_firewall_rules', 'patchstack_update_license_status', 'patchstack_update_plugins', 'patchstack_send_ping', 'puc_cron_check_updates-webarx' );
281 + foreach ( $tasks as $task ) {
282 + wp_clear_scheduled_hook( $task );
283 + }
284 +
285 + // Cleanup the .htaccess file.
286 + $this->plugin->htaccess->cleanup_htaccess_file();
287 + }
288 +
289 + /**
290 + * Activate or deactivate a license on the current site.
291 + *
292 + * @param integer $id
293 + * @param string $secret
294 + * @param string $action
295 + * @return array
296 + */
297 + public function alter_license( $id, $secret, $action ) {
298 + // Store current keys in tmp variable so in case it fails, we can set it back.
299 + $tmp_id = get_option( 'patchstack_clientid' );
300 + $tmp_key = get_option( 'patchstack_secretkey' );
301 + update_option( 'patchstack_clientid', $id );
302 + update_option( 'patchstack_secretkey', $secret );
303 +
304 + // Activate the license.
305 + if ( $action == 'activate' ) {
306 + $api_result = $this->plugin->api->get_access_token( $id, $secret, true );
307 +
308 + // Valid result?
309 + if ( ! $api_result ) {
310 + update_option( 'patchstack_clientid', $tmp_id );
311 + update_option( 'patchstack_secretkey', $tmp_key );
312 + return array(
313 + 'result' => 'error',
314 + 'message' => 'Cannot activate license!',
315 + );
316 + }
317 +
318 + // If we have an access token, tell our API that the firewall is activated
319 + // and the current URL of the site.
320 + update_option( 'patchstack_license_activated', '1' );
321 + $this->plugin->api->update_license_status();
322 + $token = $this->plugin->api->get_access_token();
323 + if ( ! empty( $token ) ) {
324 + do_action( 'patchstack_send_software_data' );
325 + if ( get_option( 'patchstack_license_free', 0 ) != 1 ) {
326 + do_action( 'patchstack_post_firewall_rules' );
327 + do_action( 'patchstack_post_dynamic_firewall_rules' );
328 + }
329 +
330 + $this->plugin->api->update_firewall_status( array( 'status' => $this->get_option( 'patchstack_basic_firewall' ) == 1 ) );
331 + $this->plugin->api->update_url( array( 'plugin_url' => get_option( 'siteurl' ) ) );
332 + }
333 + return array(
334 + 'result' => 'success',
335 + 'message' => 'License activated!',
336 + );
337 + }
338 +
339 + // Deactivate the license.
340 + if ( $action == 'deactivate' ) {
341 + update_option( 'patchstack_api_token', '' );
342 + update_option( 'patchstack_license_activated', '0' );
343 + return array(
344 + 'result' => 'success',
345 + 'message' => 'License deactivated!',
346 + );
347 + }
348 + }
349 +}