Read about Safety Net or create issues/suggestions in the
235 | Safety Net repository.
236 |
237 |
238 |
239 | It appears that you are are viewing this page on a production site.
240 | For Safety Net to run - and to access the tools on this page - the environment type needs to be set as staging, development, or local.
241 | More info in the README.
242 |
243 |
244 |
Tools
245 |
252 |
253 |
254 | true,
271 | 'message' => esc_html__( 'You can not run these tools on a production site. Please set the environment type correctly.' ),
272 | )
273 | );
274 | die();
275 | }
276 |
277 | // Permissions and security checks.
278 | check_the_permissions();
279 | check_the_nonce( $_POST['nonce'], 'safety-net-scrub-options' ); // phpcs:ignore WordPress.Security.NonceVerification
280 |
281 | // Checks passed. Scrub the options.
282 | scrub_options();
283 |
284 | // Send the AJAX response.
285 | echo wp_json_encode(
286 | array(
287 | 'success' => true,
288 | 'message' => esc_html__( 'Options have been scrubbed.' ),
289 | )
290 | );
291 |
292 | die();
293 | }
294 |
295 | /**
296 | * Handles the AJAX request for deactivating plugins.
297 | *
298 | * @return void
299 | */
300 | function handle_ajax_deactivate_plugins() {
301 |
302 | // If we're not on staging, development, or a local environment, die with a warning.
303 | if ( is_production() ) {
304 | // Send an AJAX warning.
305 | echo wp_json_encode(
306 | array(
307 | 'warning' => true,
308 | 'message' => esc_html__( 'You can not run these tools on a production site. Please set the environment type correctly.' ),
309 | )
310 | );
311 | die();
312 | }
313 |
314 | // Permissions and security checks.
315 | check_the_permissions();
316 | check_the_nonce( $_POST['nonce'], 'safety-net-deactivate-plugins' ); // phpcs:ignore WordPress.Security.NonceVerification
317 |
318 | // Checks passed. Scrub the options.
319 | deactivate_plugins();
320 |
321 | // Send the AJAX response.
322 | echo wp_json_encode(
323 | array(
324 | 'success' => true,
325 | 'message' => esc_html__( 'Plugins have been deactivated.' ),
326 | )
327 | );
328 |
329 | die();
330 | }
331 |
332 | /**
333 | * Handles the AJAX request for deleting all users.
334 | *
335 | * @return void
336 | */
337 | function handle_ajax_delete_users() {
338 |
339 | // If we're not on staging, development, or a local environment, die with a warning.
340 | if ( is_production() ) {
341 | // Send an AJAX warning.
342 | echo wp_json_encode(
343 | array(
344 | 'warning' => true,
345 | 'message' => esc_html__( 'You can not run these tools on a production site. Please set the environment type correctly.' ),
346 | )
347 | );
348 | die();
349 | }
350 |
351 | // Permissions and security checks.
352 | check_the_permissions();
353 | check_the_nonce( $_POST['nonce'], 'safety-net-delete-users' ); // phpcs:ignore WordPress.Security.NonceVerification
354 |
355 | // Checks passed. Delete the users.
356 | delete_users_and_orders();
357 |
358 | echo wp_json_encode(
359 | array(
360 | 'success' => true,
361 | 'message' => esc_html__( 'Users, orders, and subscriptions have been successfully deleted!' ),
362 | )
363 | );
364 |
365 | die();
366 | }
367 |
368 | /**
369 | * Handles the AJAX request for deleting transients.
370 | *
371 | * @return void
372 | */
373 | function handle_ajax_delete_transients() {
374 |
375 | // Permissions and security checks.
376 | check_the_permissions();
377 | check_the_nonce( $_POST['nonce'], 'safety-net-delete-transients' ); // phpcs:ignore WordPress.Security.NonceVerification
378 |
379 | // Checks passed. Delete the transients.
380 | delete_transients();
381 |
382 | echo wp_json_encode(
383 | array(
384 | 'success' => true,
385 | 'message' => esc_html__( 'Transients have been deleted.' ),
386 | )
387 | );
388 |
389 | die();
390 | }
391 |
392 | /**
393 | * Handles the AJAX request for disabling webhooks.
394 | *
395 | * @return void
396 | */
397 | function handle_ajax_disable_webhooks() {
398 |
399 | // Permissions and security checks.
400 | check_the_permissions();
401 | check_the_nonce( $_POST['nonce'], 'safety-net-disable-webhooks' ); // phpcs:ignore WordPress.Security.NonceVerification
402 |
403 | // Checks passed. Disable the webhooks.
404 | disable_webhooks();
405 |
406 | echo wp_json_encode(
407 | array(
408 | 'success' => true,
409 | 'message' => esc_html__( 'Webhooks have been disabled.' ),
410 | )
411 | );
412 |
413 | die();
414 | }
415 |
416 | /**
417 | * Checks if the user has the correct permissions, and sends the AJAX response if they don't.
418 | *
419 | * @return void
420 | */
421 | function check_the_permissions() {
422 | if ( ! current_user_can( 'manage_options' ) ) {
423 | echo wp_json_encode(
424 | array(
425 | 'success' => false,
426 | 'message' => esc_html__( 'You do not have permission to do that.' ),
427 | )
428 | );
429 |
430 | die();
431 | }
432 | }
433 |
434 | /**
435 | * Checks if the nonce passed is correct, and sends the AJAX response if it doesn't.
436 | *
437 | * @param string $nonce The nonce to check.
438 | * @param string $action The action the nonce was created from.
439 | *
440 | * @return void
441 | */
442 | function check_the_nonce( string $nonce, $action ) {
443 | if ( ! wp_verify_nonce( $nonce, $action ) ) {
444 | echo wp_json_encode(
445 | array(
446 | 'success' => false,
447 | 'message' => esc_html__( 'Security check failed. Refresh the page and try again.' ),
448 | )
449 | );
450 |
451 | die();
452 | }
453 | }
454 |
455 | /**
456 | * Adds the action link on plugins page
457 | *
458 | * @return array
459 | */
460 |
461 | function add_action_links( $actions ) {
462 | $links = array(
463 | 'Tools',
464 | );
465 |
466 | return array_merge( $actions, $links );
467 | }
468 |
469 | /**
470 | * Pause WooCommerce Subscriptions renewal and failed payment retry scheduled actions
471 | *
472 | */
473 | function pause_renewal_actions() {
474 | if ( 'on' === get_option( 'safety_net_pause_renewal_actions_toggle' ) ) {
475 | require_once __DIR__ . '/classes/class-actionscheduler-custom-dbstore.php';
476 | add_filter(
477 | 'action_scheduler_store_class',
478 | function ( $class ) {
479 | return 'SafetyNet\ActionScheduler_Custom_DBStore';
480 | },
481 | 101,
482 | 1
483 | );
484 | }
485 | }
486 |
487 | /**
488 | * Display Warning that Safety Net is activated.
489 | *
490 | */
491 | function show_warning() {
492 | // If we're not on staging, development, or a local environment, return.
493 | if ( is_production() ) {
494 | return;
495 | }
496 | echo "\n
";
497 | echo '';
498 | esc_html_e( 'Safety Net Activated', 'safety-net' );
499 | echo ': ';
500 | echo '';
501 | esc_html_e( 'The Safety Net plugin is currently active', 'safety-net' );
502 | echo ' ';
503 | if ( 'on' === get_option( 'safety_net_pause_renewal_actions_toggle' ) ) {
504 | esc_html_e( 'WooCommerce Subscriptions scheduled actions are currently paused.', 'safety-net' );
505 | echo ' ';
506 | }
507 | echo 'This site\'s environment type is set to "' . esc_html( wp_get_environment_type() ) . '".';
508 | echo '
';
509 | }
510 |
511 | /**
512 | * Stop all emails except password resets
513 | *
514 | */
515 | function stop_emails( $return, $args ) {
516 | if ( ! strstr( $args['subject'], 'Password Reset' ) ) {
517 | error_log( "Email blocked: " . $args['subject'] ); // phpcs:ignore -- Logging is okay here.
518 | // returning false says "short-circuit the wp_mail() function and indicate we did not send the email"
519 | $return = false;
520 | } else {
521 | error_log( "Email sent: " . $args['subject'] ); // phpcs:ignore -- Logging is okay here.
522 | // returning null says "don't short circuit the wp_mail function"
523 | $return = null;
524 | }
525 |
526 | return $return;
527 | }
528 |
--------------------------------------------------------------------------------
/includes/bootstrap.php:
--------------------------------------------------------------------------------
1 | update() because of the <= condition.
19 | $update = "UPDATE {$wpdb->actionscheduler_actions} SET claim_id=%d, last_attempt_gmt=%s, last_attempt_local=%s";
20 | $params = array(
21 | $claim_id,
22 | $now->format( 'Y-m-d H:i:s' ),
23 | current_time( 'mysql' ),
24 | );
25 |
26 | $where = "WHERE claim_id = 0 AND scheduled_date_gmt <= %s AND status=%s AND hook NOT IN ( 'woocommerce_scheduled_subscription_payment', 'woocommerce_scheduled_subscription_payment_retry', 'woocommerce_scheduled_subscription_end_of_prepaid_term' )";
27 | $params[] = $date->format( 'Y-m-d H:i:s' );
28 | $params[] = self::STATUS_PENDING;
29 |
30 | if ( ! empty( $hooks ) ) {
31 | $remove_these = array( 'woocommerce_scheduled_subscription_payment', 'woocommerce_scheduled_subscription_payment_retry', 'woocommerce_scheduled_subscription_end_of_prepaid_term' );
32 | $hooks = array_diff( $hooks, $remove_these );
33 |
34 | $placeholders = array_fill( 0, count( $hooks ), '%s' );
35 | $where .= ' AND hook IN (' . join( ', ', $placeholders ) . ')';
36 | $params = array_merge( $params, array_values( $hooks ) );
37 | }
38 |
39 | if ( ! empty( $group ) ) {
40 |
41 | $group_id = $this->get_group_id( $group, false );
42 |
43 | // throw exception if no matching group found, this matches ActionScheduler_wpPostStore's behaviour.
44 | if ( empty( $group_id ) ) {
45 | /* translators: %s: group name */
46 | throw new InvalidArgumentException( sprintf( __( 'The group "%s" does not exist.', 'woocommerce' ), $group ) );
47 | }
48 |
49 | $where .= ' AND group_id = %d';
50 | $params[] = $group_id;
51 | }
52 |
53 | /**
54 | * Sets the order-by clause used in the action claim query.
55 | *
56 | * @since 3.4.0
57 | *
58 | * @param string $order_by_sql
59 | */
60 | $order = apply_filters( 'action_scheduler_claim_actions_order_by', 'ORDER BY attempts ASC, scheduled_date_gmt ASC, action_id ASC' );
61 | $params[] = $limit;
62 |
63 | $sql = $wpdb->prepare( "{$update} {$where} {$order} LIMIT %d", $params ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders
64 | $rows_affected = $wpdb->query( $sql ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
65 | if ( false === $rows_affected ) {
66 | throw new RuntimeException( __( 'Unable to claim actions. Database error.', 'action-scheduler' ) );
67 | }
68 |
69 | return (int) $rows_affected;
70 | }
71 |
72 | }
73 |
--------------------------------------------------------------------------------
/includes/classes/cli/class-safetynet-cli.php:
--------------------------------------------------------------------------------
1 | false,
18 | 'message' => esc_html__( 'Safety Net Error: options need to be scrubbed first.' ),
19 | )
20 | );
21 |
22 | die();
23 | }
24 |
25 | if ( ! function_exists( 'get_plugins' ) ) {
26 | require_once ABSPATH . 'wp-admin/includes/plugin.php';
27 | }
28 |
29 | $all_installed_plugins = array_keys( get_plugins() );
30 |
31 | $denylisted_plugins = apply_filters( 'safety_net_denylisted_plugins', get_denylist_array( 'plugins' ) );
32 |
33 | // let's tack on all the Woo payment methods, in case we can deactivate any of those too
34 | if ( class_exists( 'woocommerce' ) ) {
35 | $installed_payment_methods = array_keys( WC()->payment_gateways->payment_gateways() );
36 | foreach ( $installed_payment_methods as $key => $installed_payment_method ) {
37 | $installed_payment_method = str_replace( '_', '-', $installed_payment_method );
38 | $denylisted_plugins[] = $installed_payment_method;
39 | }
40 | }
41 |
42 | foreach ( $all_installed_plugins as $key => $installed_plugin ) {
43 |
44 | if ( stristr( $installed_plugin, 'safety-net' ) ) {
45 | continue;
46 | }
47 |
48 | foreach ( $denylisted_plugins as $denylisted_plugin ) {
49 |
50 | // denylist can be partial matches, i.e. 'paypal' will match with any plugin that has 'paypal' in the slug
51 | if ( stristr( $installed_plugin, $denylisted_plugin ) ) {
52 |
53 | // remove plugin silently from active plugins list without triggering hooks
54 | $current = get_option( 'active_plugins', array() );
55 | // phpcs:ignore WordPress.PHP.StrictInArray.MissingTrueStrict
56 | $key = array_search( $installed_plugin, $current );
57 | if ( false !== $key ) {
58 | array_splice( $current, $key, 1 );
59 | }
60 | update_option( 'active_plugins', $current );
61 | break; // break out of nested loop once plugin has been deactivated
62 |
63 | }
64 | }
65 | }
66 |
67 | update_option( 'safety_net_plugins_deactivated', true );
68 | }
--------------------------------------------------------------------------------
/includes/delete-transients.php:
--------------------------------------------------------------------------------
1 | query( "DELETE FROM $wpdb->options WHERE option_name LIKE '_transient_%'" );
18 |
19 | // Set option so this function doesn't run again.
20 | update_option( 'safety_net_transients_deleted', true );
21 |
22 | wp_cache_flush();
23 | }
24 |
--------------------------------------------------------------------------------
/includes/delete.php:
--------------------------------------------------------------------------------
1 | false,
21 | 'message' => esc_html__( 'Safety Net Error: plugins need to be deactivated first.' ),
22 | )
23 | );
24 |
25 | die();
26 | }
27 |
28 | global $wpdb;
29 |
30 | // Delete orders and subscriptions
31 | $table_name = $wpdb->prefix . 'woocommerce_order_itemmeta';
32 | if ( $wpdb->get_var( $wpdb->prepare( 'SHOW TABLES LIKE %s', $table_name ) ) === $table_name ) {
33 | $wpdb->query( "DELETE FROM {$wpdb->prefix}woocommerce_order_itemmeta" );
34 | }
35 | $table_name = $wpdb->prefix . 'woocommerce_order_items';
36 | if ( $wpdb->get_var( $wpdb->prepare( 'SHOW TABLES LIKE %s', $table_name ) ) === $table_name ) {
37 | $wpdb->query( "DELETE FROM {$wpdb->prefix}woocommerce_order_items" );
38 | }
39 | $wpdb->query( "DELETE FROM $wpdb->comments WHERE comment_type = 'order_note'" );
40 | $wpdb->query( "DELETE FROM $wpdb->postmeta WHERE post_id IN ( SELECT ID FROM {$wpdb->posts} WHERE post_type = 'shop_order' OR post_type = 'shop_subscription' )" );
41 | $wpdb->query( "DELETE FROM $wpdb->posts WHERE post_type = 'shop_order'" );
42 | $wpdb->query( "DELETE FROM $wpdb->posts WHERE post_type = 'shop_subscription'" );
43 |
44 | // Delete Woo memberships
45 | $wpdb->query( "DELETE FROM $wpdb->postmeta WHERE post_id IN ( SELECT ID FROM {$wpdb->posts} WHERE post_type = 'wc_user_membership' )" );
46 | $wpdb->query( "DELETE FROM $wpdb->posts WHERE post_type = 'wc_user_membership'" );
47 |
48 | // Delete data from the High Performance Order Tables
49 | $table_name = $wpdb->prefix . 'wc_orders';
50 | if ( $wpdb->get_var( $wpdb->prepare( 'SHOW TABLES LIKE %s', $table_name ) ) === $table_name ) {
51 | $wpdb->query( "DELETE FROM {$wpdb->prefix}wc_orders" );
52 | $wpdb->query( "DELETE FROM {$wpdb->prefix}wc_order_addresses" );
53 | $wpdb->query( "DELETE FROM {$wpdb->prefix}wc_order_operational_data" );
54 | $wpdb->query( "DELETE FROM {$wpdb->prefix}wc_orders_meta" );
55 | }
56 |
57 | // Delete Woo API keys
58 | $table_name = $wpdb->prefix . 'woocommerce_api_keys';
59 | if ( $wpdb->get_var( $wpdb->prepare( 'SHOW TABLES LIKE %s', $table_name ) ) === $table_name ) {
60 | $wpdb->query( "DELETE FROM {$wpdb->prefix}woocommerce_api_keys" );
61 | }
62 |
63 | // Delete Woo webhooks
64 | $table_name = $wpdb->prefix . 'wc_webhooks';
65 | if ( $wpdb->get_var( $wpdb->prepare( 'SHOW TABLES LIKE %s', $table_name ) ) === $table_name ) {
66 | $wpdb->query( "DELETE FROM {$wpdb->prefix}wc_webhooks" );
67 | }
68 |
69 | // Delete Woo payment tokens
70 | $table_name = $wpdb->prefix . 'woocommerce_payment_tokens';
71 | if ( $wpdb->get_var( $wpdb->prepare( 'SHOW TABLES LIKE %s', $table_name ) ) === $table_name ) {
72 | $wpdb->query( "DELETE FROM {$wpdb->prefix}woocommerce_payment_tokens" );
73 | $wpdb->query( "DELETE FROM {$wpdb->prefix}woocommerce_payment_tokenmeta" );
74 | }
75 |
76 | // Delete Woo customers and analytics
77 | $table_name = $wpdb->prefix . 'wc_customer_lookup';
78 | if ( $wpdb->get_var( $wpdb->prepare( 'SHOW TABLES LIKE %s', $table_name ) ) === $table_name ) {
79 | $wpdb->query( "DELETE FROM {$wpdb->prefix}wc_customer_lookup" );
80 | $wpdb->query( "DELETE FROM {$wpdb->prefix}wc_order_product_lookup" );
81 | $wpdb->query( "DELETE FROM {$wpdb->prefix}woocommerce_log" );
82 | $wpdb->query( "DELETE FROM {$wpdb->prefix}wc_order_stats" );
83 | }
84 |
85 | // Delete renewal scheduled actions
86 | $table_name = $wpdb->prefix . 'actionscheduler_logs'; // check if table exists before purging
87 | if ( $wpdb->get_var( $wpdb->prepare( 'SHOW TABLES LIKE %s', $table_name ) ) === $table_name ) {
88 | $wpdb->query( "DELETE lg FROM {$wpdb->prefix}actionscheduler_logs lg LEFT JOIN {$wpdb->prefix}actionscheduler_actions aa ON aa.action_id = lg.action_id WHERE aa.hook IN ( 'woocommerce_scheduled_subscription_payment', 'woocommerce_scheduled_subscription_payment_retry', 'woocommerce_scheduled_subscription_end_of_prepaid_term' )" );
89 | }
90 | $table_name = $wpdb->prefix . 'actionscheduler_actions'; // check if table exists before purging
91 | if ( $wpdb->get_var( $wpdb->prepare( 'SHOW TABLES LIKE %s', $table_name ) ) === $table_name ) {
92 | $wpdb->query( "DELETE FROM {$wpdb->prefix}actionscheduler_actions WHERE hook IN ( 'woocommerce_scheduled_subscription_payment', 'woocommerce_scheduled_subscription_payment_retry', 'woocommerce_scheduled_subscription_end_of_prepaid_term' )" );
93 | }
94 |
95 | // Delete WP Mail Logging logs
96 | $table_name = $wpdb->prefix . 'wpml_mails'; // check if table exists before purging
97 | if ( $wpdb->get_var( $wpdb->prepare( 'SHOW TABLES LIKE %s', $table_name ) ) === $table_name ) {
98 | $wpdb->query( "DELETE FROM {$wpdb->prefix}wpml_mails" );
99 | }
100 |
101 | // Delete Newsletter plugin subscribers
102 | $table_name = $wpdb->prefix . 'newsletter';
103 | if ( $wpdb->get_var( $wpdb->prepare( 'SHOW TABLES LIKE %s', $table_name ) ) === $table_name ) {
104 | $wpdb->query( "DELETE FROM {$wpdb->prefix}newsletter" );
105 | }
106 |
107 | // Delete Give plugin data
108 | $table_name = $wpdb->prefix . 'give_donors';
109 | if ( $wpdb->get_var( $wpdb->prepare( 'SHOW TABLES LIKE %s', $table_name ) ) === $table_name ) {
110 | $wpdb->query( "DELETE FROM {$wpdb->prefix}give_donors" );
111 | $wpdb->query( "DELETE FROM {$wpdb->prefix}give_donormeta" );
112 | $wpdb->query( "DELETE FROM {$wpdb->prefix}give_donationmeta" );
113 | $wpdb->query( "DELETE FROM {$wpdb->prefix}give_comments" );
114 | $wpdb->query( "DELETE FROM {$wpdb->prefix}give_commentmeta" );
115 | $wpdb->query( "DELETE FROM {$wpdb->prefix}give_sessions" );
116 | $wpdb->query( "DELETE FROM {$wpdb->prefix}give_subscriptions" );
117 | $wpdb->query( "DELETE FROM {$wpdb->prefix}give_subscriptionmeta" );
118 | }
119 |
120 | // Delete Give payment and donation posts
121 | $wpdb->query( "DELETE FROM $wpdb->postmeta WHERE post_id IN ( SELECT ID FROM {$wpdb->posts} WHERE post_type = 'give_forms' )" );
122 | $wpdb->query( "DELETE FROM $wpdb->posts WHERE post_type = 'give_forms'" );
123 | $wpdb->query( "DELETE FROM $wpdb->postmeta WHERE post_id IN ( SELECT ID FROM {$wpdb->posts} WHERE post_type = 'give_payment' )" );
124 | $wpdb->query( "DELETE FROM $wpdb->posts WHERE post_type = 'give_payment'" );
125 |
126 | // Reassigning all posts to the first admin user
127 | reassign_all_posts();
128 |
129 | $admins = get_admin_user_ids(); // returns an array of ids
130 |
131 | // Delete all non-admin users and their user meta
132 | $placeholders = implode( ',', array_fill( 0, count( $admins ), '%d' ) );
133 | $wpdb->query( $wpdb->prepare( "DELETE FROM $wpdb->usermeta WHERE user_id NOT IN ($placeholders)", ...$admins ) ); // phpcs:ignore
134 | $wpdb->query( $wpdb->prepare( "DELETE FROM $wpdb->users WHERE ID NOT IN ($placeholders)", ...$admins ) ); // phpcs:ignore
135 |
136 | // Set option so this function doesn't run again.
137 | update_option( 'safety_net_data_deleted', true );
138 |
139 | wp_cache_flush();
140 | }
141 |
142 | /**
143 | * Reassigns all posts to an admin.
144 | *
145 | * @return void
146 | */
147 | function reassign_all_posts() {
148 | global $wpdb;
149 |
150 | $wpdb->get_results( $wpdb->prepare( "UPDATE $wpdb->posts SET post_author = %d", get_admin_id() ) );
151 |
152 | wp_cache_flush();
153 | }
154 |
155 | /**
156 | * Returns an admin ID that posts can be reassigned to.
157 | *
158 | * @return mixed
159 | */
160 | function get_admin_id() {
161 | $admin = get_users(
162 | array(
163 | 'role__in' => array(
164 | 'administrator',
165 | ),
166 | 'fields' => 'ids',
167 | 'number' => 1,
168 | )
169 | );
170 |
171 | return $admin[0];
172 | }
173 |
--------------------------------------------------------------------------------
/includes/disable-webhooks.php:
--------------------------------------------------------------------------------
1 | query( "UPDATE {$wpdb->prefix}wc_webhooks SET status = 'disabled'" );
17 |
18 | // Set option so this function doesn't run again.
19 | update_option( 'safety_net_webhooks_disabled', true );
20 |
21 | wp_cache_flush();
22 | }
--------------------------------------------------------------------------------
/includes/scrub-options.php:
--------------------------------------------------------------------------------
1 | array(
68 | 'aes_key',
69 | 'hmac_key',
70 | ),
71 | );
72 | $option_array = $option_value;
73 | foreach ( $keys_to_scrub as $index => $keys ) {
74 | if ( array_key_exists( $index, $option_array ) ) {
75 | foreach ( $keys as $key ) {
76 | if ( array_key_exists( $key, $option_array[ $index ] ) ) {
77 | $option_array[ $index ][ $key ] = '';
78 | }
79 | }
80 | }
81 | }
82 | safety_net_update_option_direct( $option, $option_array );
83 | } else {
84 | // Some plugins don't like it when options are deleted, so we will save their value as either an empty string or array, depending on which it already is.
85 | if ( is_array( get_option( $option ) ) ) {
86 | $empty_array = array();
87 | safety_net_update_option_direct( $option, $empty_array );
88 | } else {
89 | safety_net_update_option_direct( $option, '' );
90 | }
91 | }
92 | }
93 | }
94 |
95 | // Disable all Woo Webhooks
96 | if ( class_exists( 'WooCommerce' ) ) {
97 | $data_store = WC_Data_Store::load( 'webhook' );
98 | $webhooks = $data_store->search_webhooks();
99 |
100 | if ( ! empty( $webhooks ) ) {
101 | foreach ( $webhooks as $webhook_id ) {
102 | $webhook = new WC_Webhook( $webhook_id );
103 | $webhook->set_status( 'disabled' );
104 | $webhook->save();
105 | }
106 | }
107 | }
108 |
109 | // Disable AutomateWoo workflows, clear the queue, and set scheduled actions to "done"
110 | $wpdb->query( "UPDATE $wpdb->posts SET post_status = 'aw-disabled' WHERE post_type = 'aw_workflow' AND post_status = 'publish'" );
111 |
112 | $table_name = $wpdb->prefix . 'automatewoo_queue';
113 | if ( $wpdb->get_var( $wpdb->prepare( 'SHOW TABLES LIKE %s', $table_name ) ) === $table_name ) {
114 | $wpdb->query( "DELETE FROM {$wpdb->prefix}automatewoo_queue" );
115 | $wpdb->query( "DELETE FROM {$wpdb->prefix}automatewoo_queue_meta" );
116 | }
117 |
118 | $table_name = $wpdb->prefix . 'actionscheduler_actions';
119 | if ( $wpdb->get_var( $wpdb->prepare( 'SHOW TABLES LIKE %s', $table_name ) ) === $table_name ) {
120 | $wpdb->query( "UPDATE {$wpdb->prefix}actionscheduler_actions SET status = 'done' WHERE status = 'pending' AND hook LIKE '%automatewoo%'" );
121 | }
122 |
123 | update_option( 'safety_net_options_scrubbed', true );
124 |
125 | // Clear object cache since the updates happen directly in the database.
126 | wp_cache_flush();
127 | }
128 |
129 | /**
130 | * Updates options directly in the database to prevent notifications from being sent.
131 | *
132 | * @param string $option_name The name of the option to update.
133 | * @param mixed $option_value The value to set the option to.
134 | *
135 | * @return void
136 | */
137 | function safety_net_update_option_direct( $option_name, $option_value ) {
138 | global $wpdb;
139 |
140 | if ( is_array( $option_value ) ) {
141 | $option_value = serialize( $option_value );
142 | }
143 |
144 | $wpdb->update(
145 | $wpdb->options,
146 | array( 'option_value' => $option_value ),
147 | array( 'option_name' => $option_name ),
148 | );
149 | }
150 |
--------------------------------------------------------------------------------
/includes/utilities.php:
--------------------------------------------------------------------------------
1 | get_col( "SELECT u.ID FROM $wpdb->users u INNER JOIN $wpdb->usermeta m ON m.user_id = u.ID WHERE m.meta_key = '{$wpdb->prefix}capabilities' AND m.meta_value LIKE '%administrator%' ORDER BY u.user_registered" );
14 | }
15 |
16 | /**
17 | * Returns true if plugin is running on production
18 | *
19 | * @return boolean
20 | */
21 | function is_production() {
22 | // If we're not on staging, development, or a local environment, return true.
23 | if ( ! in_array( wp_get_environment_type(), array( 'staging', 'development', 'local' ), true ) ) {
24 | return true;
25 | }
26 | }
27 |
28 | /**
29 | * Reads the plugin or options denylist txt files, and returns an array for use
30 | *
31 | * @param string $denylist_type Type of denylist. Accepts 'options' or 'plugins'.
32 | *
33 | * @return array
34 | */
35 | function get_denylist_array( $denylist_type ): array {
36 | global $wp_filesystem;
37 |
38 | if ( ! $wp_filesystem ) {
39 | require_once ABSPATH . 'wp-admin/includes/file.php';
40 | WP_Filesystem();
41 | }
42 |
43 | $denylist_array = array();
44 | $filename = 'options' === $denylist_type ? 'option_scrublist.txt' : 'plugin_denylist.txt';
45 | $file_path = SAFETY_NET_PATH . '/assets/data/' . $filename;
46 |
47 | if ( ! $wp_filesystem->exists( $file_path ) ) {
48 | return $denylist_array;
49 | }
50 |
51 | $file_contents = $wp_filesystem->get_contents( $file_path );
52 | if ( false === $file_contents ) {
53 | return $denylist_array;
54 | }
55 |
56 | $rows = explode( "\n", $file_contents );
57 |
58 | foreach ( $rows as $row ) {
59 | $data = str_getcsv( $row );
60 | foreach ( $data as $item ) {
61 | $denylist_array[] = trim( $item );
62 | }
63 | }
64 |
65 | return array_filter( $denylist_array );
66 | }
67 |
68 | /**
69 | * Renders an admin notice, if the plugin is running on production
70 | *
71 | * @filter safety_net_show_production_notice
72 | *
73 | * @return void
74 | */
75 | function show_production_notice() {
76 | // If not production, return.
77 | if ( ! is_production() ) {
78 | return;
79 | }
80 |
81 | // Check the if the user has the capability to manage options.
82 | $allowed = current_user_can( 'manage_options' );
83 | // Filter for third-party plugins to add their own capability check.
84 | $allowed = apply_filters( 'safety_net_show_production_notice', $allowed );
85 |
86 | if ( ! $allowed ) {
87 | return;
88 | }
89 |
90 | // Check if the constant starts as an mu plugin.
91 | $is_mu = defined( 'WPMU_PLUGIN_DIR' ) && \str_starts_with( SAFETY_NET_PATH, WPMU_PLUGIN_DIR );
92 | ?>
93 |