PluginProbe
Patchstack – WordPress & Plugins Security / 2.3.0
Patchstack – WordPress & Plugins Security v2.3.0
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 | patchstack.php +90 -29 2.1.172.3.0 View file →
@@ -1,10 +1,11 @@
1 1 <?php
2 2 /**
3 3 * Plugin Name: Patchstack Security
4 - * Plugin URI: https://patchstack.com
4 + * Plugin URI: https://patchstack.com/?utm_medium=wp&utm_source=dashboard&utm_campaign=patchstack%20plugin
5 + * Author URI: https://patchstack.com/?utm_medium=wp&utm_source=dashboard&utm_campaign=patchstack%20plugin
5 6 * Description: Patchstack identifies security vulnerabilities in WordPress plugins, themes, and core.
6 - * Version: 2.1.17
7 + * Version: 2.3.0
7 8 * Author: Patchstack
8 9 * License: GPLv3
9 10 * Text Domain: patchstack
10 11 * Domain Path: /languages
@@ -32,9 +33,9 @@
32 33
33 34 // Set up our filename.
34 35 $file_name = strtolower( str_replace( '_', '-', substr( $class_name, strlen( 'P_' ) ) ) );
35 36 $dir = trailingslashit( dirname( __FILE__ ) ) . 'includes/';
36 - $target = array( $dir . $file_name . '.php', $dir . 'admin/' . str_replace( 'admin-', '', $file_name ) . '.php' );
37 + $target = [ $dir . $file_name . '.php', $dir . 'admin/' . str_replace( 'admin-', '', $file_name ) . '.php' ];
37 38
38 39 // Attempt each target and load if it exists.
39 40 foreach ( $target as $file ) {
40 41 if ( file_exists( $file ) ) {
@@ -57,9 +58,9 @@
57 58 * The plugin version.
58 59 *
59 60 * @var string
60 61 */
61 - const VERSION = '2.1.17';
62 + const VERSION = '2.3.0';
62 63
63 64 /**
64 65 * API URL of Patchstack to communicate with.
65 66 *
@@ -113,9 +114,9 @@
113 114 * Detailed activation error messages.
114 115 *
115 116 * @var array
116 117 */
117 - protected $activation_errors = array();
118 + protected $activation_errors = [];
118 119
119 120 /**
120 121 * Singleton instance of plugin.
121 122 *
@@ -142,9 +143,8 @@
142 143 protected $hide_login;
143 144 protected $listener;
144 145 protected $event_log;
145 146 protected $multisite;
146 - protected $notice;
147 147 protected $admin_ajax;
148 148 protected $admin_general;
149 149 protected $admin_menu;
150 150 protected $admin_options;
@@ -169,8 +169,13 @@
169 169 $this->basename = plugin_basename( __FILE__ );
170 170 $this->url = plugin_dir_url( __FILE__ );
171 171 $names = explode( '/', $this->basename );
172 172 $this->name = $names[0];
173 +
174 + // Define WP_CLI command.
175 + if ( defined( 'WP_CLI' ) && WP_CLI && method_exists('\WP_CLI', 'add_command')) {
176 + \WP_CLI::add_command( 'patchstack activate', [ $this, 'cli_activate' ] );
177 + }
173 178 }
174 179
175 180 /**
176 181 * Call the constructor of all the Patchstack related classes.
@@ -178,9 +183,9 @@
178 183 * @return void
179 184 */
180 185 public function plugin_classes() {
181 186 // Define the array of the classes.
182 - foreach ( array(
187 + foreach ( [
183 188 'admin_options' => 'P_Admin_Options',
184 189 'cron' => 'P_Cron',
185 190 'api' => 'P_Api',
186 191 'login' => 'P_Login',
@@ -190,20 +195,20 @@
190 195 'hacker_log' => 'P_Hacker_Log',
191 196 'upload' => 'P_Upload',
192 197 'rules' => 'P_Rules',
193 198 'hide_login' => 'P_Hide_Login',
194 - 'listener' => 'P_Listener',
195 199 'event_log' => 'P_Event_Log',
196 200 'activation' => 'P_Activation',
201 + 'listener' => 'P_Listener',
197 202 'multisite' => 'P_Multisite',
198 - 'notice' => 'P_Cookie_Notice',
199 203 'admin_ajax' => 'P_Admin_Ajax',
200 204 'admin_general' => 'P_Admin_General',
201 205 'admin_menu' => 'P_Admin_Menu',
202 - ) as $var => $class ) {
206 + ] as $var => $class ) {
203 207 $this->$var = new $class( $this );
204 208 }
205 209
210 + // Load firewall base functionality.
206 211 $this->firewall_base = new P_Firewall( true, $this, true );
207 212 }
208 213
209 214 /**
@@ -216,8 +221,52 @@
216 221 $this->activation->activate( $this );
217 222 }
218 223
219 224 /**
225 + * Connects the Patchstack plugin to the API with the license id and secret key.
226 + *
227 + * Returns an error if the connection was not successful.
228 + *
229 + * ## OPTIONS
230 + *
231 + * [<id>]
232 + * : The API client id.
233 + *
234 + * [<secret>]
235 + * : The API secret key.
236 + *
237 + * <secret-id>
238 + * : The API client id and secret key merged together, found in the App. E.g. 2b072e8b60402e30d481df351fc08183906254e0-123456
239 + *
240 + * ## EXAMPLES
241 + *
242 + * $ wp patchstack activate 123456 2b072e8b60402e30d481df351fc08183906254e0
243 + * Success: The Patchstack plugin has been successfully connected.
244 + *
245 + * or
246 + *
247 + * $ wp patchstack activate 2b072e8b60402e30d481df351fc08183906254e0-123456
248 + * Success: The Patchstack plugin has been successfully connected.
249 + */
250 + public function cli_activate( $args ) {
251 + // Handle both ways to activate the plugin.
252 + if ( count( $args ) === 1 && strpos( $args[0], '-' ) !== false ) {
253 + list( $secret, $id ) = explode( '-', $args[0] );
254 + } else {
255 + $id = isset( $args[0] ) ? trim( $args[0] ) : '';
256 + $secret = isset( $args[1] ) ? trim( $args[1] ) : '';
257 + }
258 +
259 + $result = $this->activation->alter_license( $id, $secret, 'activate' );
260 + if ( $result['result'] == 'error' ) {
261 + \WP_CLI::error( "The Patchstack plugin could not be connected. Make sure the id and secret key are valid and that api.patchstack.com is not blocked. Additional information:\n" . $result['body'] );
262 + return;
263 + }
264 +
265 + \WP_CLI::success( 'The Patchstack plugin has been successfully connected.' );
266 + }
267 +
268 + /**
220 269 * Deactivate the plugin.
221 270 *
222 271 * @return void
223 272 */
@@ -226,24 +275,23 @@
226 275 $this->activation->deactivate();
227 276 }
228 277
229 278 /**
230 - * Boot Patchstack and its classes.
231 - *
279 + * Load translated strings for the plugin.
280 + *
232 281 * @return void
233 282 */
234 - public function hooks() {
235 - add_action( 'init', array( $this, 'init' ), ~PHP_INT_MAX );
283 + public function load_textdomain () {
284 + load_plugin_textdomain( 'patchstack', false, dirname( $this->basename ) . '/languages/' );
236 285 }
237 286
238 287 /**
239 - * Boot Patchstack
288 + * Boot Patchstack.
240 289 *
241 290 * @return void
242 291 */
243 292 public function init() {
244 - // Load translated strings for plugin.
245 - load_plugin_textdomain( 'patchstack', false, dirname( $this->basename ) . '/languages/' );
293 + add_action( 'init', [ $this, 'load_textdomain' ] );
246 294
247 295 // Initialize plugin classes.
248 296 $this->plugin_classes();
249 297
@@ -254,9 +302,9 @@
254 302 if ( get_option( 'patchstack_api_token', '' ) == '' && get_option( 'patchstack_license_expiry', '' ) == '' ) {
255 303 $this->api->update_license_status();
256 304 }
257 305
258 - // Determine if the license is activated and not expired.
306 + // Run firewall if not disabled and license activated.
259 307 if ( get_option( 'patchstack_license_activated', 0 ) == 1 && get_option( 'patchstack_basic_firewall', 0 ) == 1 && get_option( 'patchstack_license_free', 0 ) == 0 ) {
260 308 $this->firewall = new P_Firewall( true, $this );
261 309 }
262 310 }
@@ -311,22 +359,35 @@
311 359 * @return void
312 360 */
313 361 function patchstack_uninstall() {
314 362 // Delete most of the Patchstack options.
315 - $options = array( 'patchstack_eventlog_lastid', 'patchstack_api_token', 'patchstack_dashboardlock', 'patchstack_pluginedit', 'patchstack_move_logs', 'patchstack_userenum', 'patchstack_basicscanblock', 'patchstack_hidewpcontent', 'patchstack_hidewpversionk', 'patchstack_prevent_default_file_access', 'patchstack_basic_firewall', 'patchstack_known_blacklist', 'patchstack_block_debug_log_access', 'patchstack_block_fake_bots', 'patchstack_index_views', 'patchstack_proxy_comment_posting', 'patchstack_bad_query_strings', 'patchstack_advanced_character_string_filter', 'patchstack_advanced_blacklist_firewall', 'patchstack_forbid_rfi', 'patchstack_image_hotlinking', 'patchstack_add_security_headers', 'patchstack_firewall_log_lastid', 'patchstack_user_log_lastid', 'patchstack_captcha_public_key', 'patchstack_captcha_private_key', 'patchstack_scan_interval', 'patchstack_scan_day', 'patchstack_scan_time', 'patchstack_hackers_log', 'patchstack_users_log', 'patchstack_visitors_log', 'external_updates-webarx', 'patchstack_wp_stats', 'patchstack_captcha_login_form', 'patchstack_license_activated', 'patchstack_license_expiry', 'patchstack_software_data_hash', 'patchstack_mv_wp_login', 'patchstack_rename_wp_login', 'patchstack_googledrive_backup_is_running', 'patchstack_googledrive_upload_state', 'patchstack_googledrive_access_token', 'patchstack_googledrive_refresh_token', 'patchstack_cron_offset', 'patchstack_htaccess_rules_hash' );
316 - foreach ( $options as $option ) {
317 - delete_option( $option );
363 + global $wpdb;
364 + $options = $wpdb->get_results( "SELECT option_name FROM $wpdb->options WHERE option_name LIKE 'patchstack_%'" );
318 365
366 + // Few options we want to keep.
367 + $keep = ['patchstack_hits_last_30', 'patchstack_hits_all_time', 'patchstack_clientid', 'patchstack_secretkey', 'patchstack_secretkey_nonce', 'patchstack_api_token'];
368 +
369 + // Delete everything else.
370 + foreach( $options as $option ) {
371 + if ( in_array( $option->option_name, $keep ) || stripos( $option->option_name, 'patchstack_captcha_' ) !== false ) {
372 + continue;
373 + }
374 +
375 + delete_option( $option->option_name );
376 +
319 377 if ( is_multisite() ) {
320 - delete_site_option( $option );
378 + delete_site_option( $option->option_name );
321 379 }
322 380 }
323 381
324 - // Drop all Patchstack tables.
382 + // Drop all tables.
325 383 global $wpdb;
326 - $tables = array( 'patchstack_user_log', 'patchstack_visitor_log', 'patchstack_firewall_log', 'patchstack_file_hashes', 'patchstack_logic', 'patchstack_ip', 'patchstack_event_log' );
327 - foreach ( $tables as $table ) {
328 - $wpdb->query( 'DROP TABLE IF EXISTS ' . $wpdb->prefix . $table );
384 + $prefixes = ['patchstack_', 'webarx_'];
385 + foreach ( $prefixes as $prefix ) {
386 + $tables = [ 'user_log', 'visitor_log', 'firewall_log', 'file_hashes', 'logic', 'ip', 'event_log' ];
387 + foreach ( $tables as $table ) {
388 + $wpdb->query( 'DROP TABLE IF EXISTS ' . $wpdb->prefix . $prefix . $table );
389 + }
329 390 }
330 391 }
331 392 }
332 393
@@ -341,10 +402,10 @@
341 402 }
342 403 }
343 404
344 405 // Kick it off.
345 -add_action( 'plugins_loaded', array( patchstack(), 'hooks' ) );
406 +add_action( 'plugins_loaded', [ patchstack(), 'init' ] );
346 407
347 408 // Activation and deactivation hooks.
348 -register_activation_hook( __FILE__, array( patchstack(), 'activate' ) );
349 -register_deactivation_hook( __FILE__, array( patchstack(), 'deactivate' ) );
409 +register_activation_hook( __FILE__, [ patchstack(), 'activate' ] );
410 +register_deactivation_hook( __FILE__, [ patchstack(), 'deactivate' ] );
350 411 register_uninstall_hook( __FILE__, 'patchstack_uninstall' );