Skip to content

Commit c765ce6

Browse files
committed
Inactive account handling
1 parent b1dfc31 commit c765ce6

8 files changed

+221
-95
lines changed

Diff for: includes/admin/settings/class-wc-settings-accounts.php

+38-26
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,25 @@ public function get_settings() {
8989
'checkboxgroup' => 'end',
9090
'autoload' => false,
9191
),
92+
array(
93+
'title' => __( 'Account erasure requests', 'woocommerce' ),
94+
'desc' => __( 'Remove personal data from orders', 'woocommerce' ),
95+
'desc_tip' => __( 'When handling an account erasure request, should personal data within orders be retained or removed?', 'woocommerce' ),
96+
'id' => 'woocommerce_erasure_request_removes_order_data',
97+
'type' => 'checkbox',
98+
'default' => 'no',
99+
'checkboxgroup' => 'start',
100+
'autoload' => false,
101+
),
102+
array(
103+
'desc' => __( 'Remove access to downloads', 'woocommerce' ),
104+
'desc_tip' => __( 'When handling an account erasure request, should access to downloadable files be revoked and download logs cleared?', 'woocommerce' ),
105+
'id' => 'woocommerce_erasure_request_removes_download_data',
106+
'type' => 'checkbox',
107+
'default' => 'no',
108+
'checkboxgroup' => 'end',
109+
'autoload' => false,
110+
),
92111
array(
93112
'type' => 'sectionend',
94113
'id' => 'account_registration_options',
@@ -133,33 +152,26 @@ public function get_settings() {
133152
'id' => 'privacy_policy_options',
134153
),
135154
array(
136-
'title' => __( 'Personal data handling', 'woocommerce' ),
137-
'desc' => __( 'These tools let you clean up personal data when it\'s no longer needed for processing, or a user requests account erasure.', 'woocommerce' ),
155+
'title' => __( 'Personal data retention', 'woocommerce' ),
156+
'desc' => __( 'Choose how long to retain personal data when it\'s no longer needed for processing. Leave the following options blank to retain this data indefinetely.', 'woocommerce' ),
138157
'type' => 'title',
139-
'id' => 'personal_data_handling',
140-
),
141-
array(
142-
'title' => __( 'Erasure request handling', 'woocommerce' ),
143-
'desc' => __( 'Remove personal data from orders', 'woocommerce' ),
144-
'desc_tip' => __( 'When processing a request for erasure, should the order data be retained or removed?', 'woocommerce' ),
145-
'id' => 'woocommerce_erasure_request_removes_order_data',
146-
'type' => 'checkbox',
147-
'default' => 'no',
148-
'checkboxgroup' => 'start',
149-
'autoload' => false,
158+
'id' => 'personal_data_retention',
150159
),
151160
array(
152-
'desc' => __( 'Remove access to downloads', 'woocommerce' ),
153-
'desc_tip' => __( 'When processing a request for erasure, should access to downloadable files be revoked and download logs cleared?', 'woocommerce' ),
154-
'id' => 'woocommerce_erasure_request_removes_download_data',
155-
'type' => 'checkbox',
156-
'default' => 'no',
157-
'checkboxgroup' => 'end',
158-
'autoload' => false,
161+
'title' => __( 'Retain inactive accounts ', 'woocommerce' ),
162+
'desc_tip' => __( 'Inactive accounts are those which have not logged in, or placed an order, for the specified duration. They will be deleted. Any orders will be converted into guest orders.', 'woocommerce' ),
163+
'id' => 'woocommerce_delete_inactive_accounts',
164+
'type' => 'relative_date_selector',
165+
'placeholder' => __( 'N/A', 'woocommerce' ),
166+
'default' => array(
167+
'number' => '',
168+
'unit' => 'months',
169+
),
170+
'autoload' => false,
159171
),
160172
array(
161173
'title' => __( 'Retain pending orders ', 'woocommerce' ),
162-
'desc_tip' => __( 'Retain orders with this status for a specified duration before trashing them. Leave blank to retain these orders forever.', 'woocommerce' ),
174+
'desc_tip' => __( 'Pending orders are unpaid and may have been abadonned by the customer. They will be trashed after the specified duration.', 'woocommerce' ),
163175
'id' => 'woocommerce_trash_pending_orders',
164176
'type' => 'relative_date_selector',
165177
'placeholder' => __( 'N/A', 'woocommerce' ),
@@ -168,7 +180,7 @@ public function get_settings() {
168180
),
169181
array(
170182
'title' => __( 'Retain failed orders', 'woocommerce' ),
171-
'desc_tip' => __( 'Retain orders with this status for a specified duration before trashing them. Leave blank to retain these orders forever.', 'woocommerce' ),
183+
'desc_tip' => __( 'Failed orders are unpaid and may have been abadonned by the customer. They will be trashed after the specified duration.', 'woocommerce' ),
172184
'id' => 'woocommerce_trash_failed_orders',
173185
'type' => 'relative_date_selector',
174186
'placeholder' => __( 'N/A', 'woocommerce' ),
@@ -177,7 +189,7 @@ public function get_settings() {
177189
),
178190
array(
179191
'title' => __( 'Retain cancelled orders', 'woocommerce' ),
180-
'desc_tip' => __( 'Retain orders with this status for a specified duration before trashing them. Leave blank to retain these orders forever.', 'woocommerce' ),
192+
'desc_tip' => __( 'Cancelled orders are unpaid and may have been cancelled by the store owner or customer. They will be trashed after the specified duration.', 'woocommerce' ),
181193
'id' => 'woocommerce_trash_cancelled_orders',
182194
'type' => 'relative_date_selector',
183195
'placeholder' => __( 'N/A', 'woocommerce' ),
@@ -186,19 +198,19 @@ public function get_settings() {
186198
),
187199
array(
188200
'title' => __( 'Retain completed orders', 'woocommerce' ),
189-
'desc_tip' => __( 'Retain completed orders for a specified duration before anonymizing the personal data within them. Leave blank to retain these orders forever.', 'woocommerce' ),
201+
'desc_tip' => __( 'Retain completed orders for a specified duration before anonymizing the personal data within them.', 'woocommerce' ),
190202
'id' => 'woocommerce_anonymize_completed_orders',
191203
'type' => 'relative_date_selector',
192204
'placeholder' => __( 'N/A', 'woocommerce' ),
193205
'default' => array(
194206
'number' => '',
195-
'unit' => 'years',
207+
'unit' => 'months',
196208
),
197209
'autoload' => false,
198210
),
199211
array(
200212
'type' => 'sectionend',
201-
'id' => 'personal_data_handling',
213+
'id' => 'personal_data_retention',
202214
),
203215
)
204216
);

Diff for: includes/class-wc-install.php

+10-4
Original file line numberDiff line numberDiff line change
@@ -351,7 +351,7 @@ private static function create_cron_jobs() {
351351
wp_clear_scheduled_hook( 'woocommerce_scheduled_sales' );
352352
wp_clear_scheduled_hook( 'woocommerce_cancel_unpaid_orders' );
353353
wp_clear_scheduled_hook( 'woocommerce_cleanup_sessions' );
354-
wp_clear_scheduled_hook( 'woocommerce_cleanup_orders' );
354+
wp_clear_scheduled_hook( 'woocommerce_cleanup_personal_data' );
355355
wp_clear_scheduled_hook( 'woocommerce_geoip_updater' );
356356
wp_clear_scheduled_hook( 'woocommerce_tracker_send_event' );
357357

@@ -365,7 +365,7 @@ private static function create_cron_jobs() {
365365
wp_schedule_single_event( time() + ( absint( $held_duration ) * 60 ), 'woocommerce_cancel_unpaid_orders' );
366366
}
367367

368-
wp_schedule_event( time(), 'daily', 'woocommerce_cleanup_orders' );
368+
wp_schedule_event( time(), 'daily', 'woocommerce_cleanup_personal_data' );
369369
wp_schedule_event( time(), 'twicedaily', 'woocommerce_cleanup_sessions' );
370370
wp_schedule_event( strtotime( 'first tuesday of next month' ), 'monthly', 'woocommerce_geoip_updater' );
371371
wp_schedule_event( time(), apply_filters( 'woocommerce_tracker_event_recurrence', 'daily' ), 'woocommerce_tracker_send_event' );
@@ -838,10 +838,16 @@ public static function create_roles() {
838838
$wp_roles = new WP_Roles(); // @codingStandardsIgnoreLine
839839
}
840840

841+
// Dummy gettext calls to get strings in the catalog.
842+
/* translators: user role */
843+
_x( 'Customer', 'User role', 'woocommerce' );
844+
/* translators: user role */
845+
_x( 'Shop manager', 'User role', 'woocommerce' );
846+
841847
// Customer role.
842848
add_role(
843849
'customer',
844-
__( 'Customer', 'woocommerce' ),
850+
'Customer',
845851
array(
846852
'read' => true,
847853
)
@@ -850,7 +856,7 @@ public static function create_roles() {
850856
// Shop manager role.
851857
add_role(
852858
'shop_manager',
853-
__( 'Shop manager', 'woocommerce' ),
859+
'Shop manager',
854860
array(
855861
'level_9' => true,
856862
'level_8' => true,

Diff for: includes/class-wc-privacy-background-process.php

+3
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,9 @@ protected function task( $item ) {
5555
case 'anonymize_completed_orders':
5656
$process_count = WC_Privacy::anonymize_completed_orders( $process_limit );
5757
break;
58+
case 'delete_inactive_accounts':
59+
$process_count = WC_Privacy::delete_inactive_accounts( $process_limit );
60+
break;
5861
}
5962

6063
if ( $process_limit === $process_count ) {

Diff for: includes/class-wc-privacy.php

+109-46
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ public function __construct() {
4545
$this->add_eraser( __( 'Customer Downloads', 'woocommerce' ), array( 'WC_Privacy_Erasers', 'download_data_eraser' ) );
4646

4747
// Cleanup orders daily - this is a callback on a daily cron event.
48-
add_action( 'woocommerce_cleanup_orders', array( $this, 'order_cleanup_process' ) );
48+
add_action( 'woocommerce_cleanup_personal_data', array( $this, 'queue_cleanup_personal_data' ) );
4949

5050
// Handles custom anonomization types not included in core.
5151
add_filter( 'wp_privacy_anonymize_data', array( $this, 'anonymize_custom_data_types' ), 10, 3 );
@@ -87,11 +87,12 @@ public function get_privacy_message() {
8787
/**
8888
* Spawn events for order cleanup.
8989
*/
90-
public function order_cleanup_process() {
90+
public function queue_cleanup_personal_data() {
9191
self::$background_process->push_to_queue( array( 'task' => 'trash_pending_orders' ) );
9292
self::$background_process->push_to_queue( array( 'task' => 'trash_failed_orders' ) );
9393
self::$background_process->push_to_queue( array( 'task' => 'trash_cancelled_orders' ) );
9494
self::$background_process->push_to_queue( array( 'task' => 'anonymize_completed_orders' ) );
95+
self::$background_process->push_to_queue( array( 'task' => 'delete_inactive_accounts' ) );
9596
self::$background_process->save()->dispatch();
9697
}
9798

@@ -119,48 +120,6 @@ public function anonymize_custom_data_types( $anonymous, $type, $data ) {
119120
return $anonymous;
120121
}
121122

122-
/**
123-
* For a given query trash all matches.
124-
*
125-
* @since 3.4.0
126-
* @param array $query Query array to pass to wc_get_orders().
127-
* @return int Count of orders that were trashed.
128-
*/
129-
protected static function trash_orders_query( $query ) {
130-
$orders = wc_get_orders( $query );
131-
$count = 0;
132-
133-
if ( $orders ) {
134-
foreach ( $orders as $order ) {
135-
$order->delete( false );
136-
$count ++;
137-
}
138-
}
139-
140-
return $count;
141-
}
142-
143-
/**
144-
* For a given query, anonymize all matches.
145-
*
146-
* @since 3.4.0
147-
* @param array $query Query array to pass to wc_get_orders().
148-
* @return int Count of orders that were anonymized.
149-
*/
150-
protected static function anonymize_orders_query( $query ) {
151-
$orders = wc_get_orders( $query );
152-
$count = 0;
153-
154-
if ( $orders ) {
155-
foreach ( $orders as $order ) {
156-
WC_Privacy_Erasers::remove_order_personal_data( $order );
157-
$count ++;
158-
}
159-
}
160-
161-
return $count;
162-
}
163-
164123
/**
165124
* Find and trash old orders.
166125
*
@@ -224,15 +183,35 @@ public static function trash_cancelled_orders( $limit = 20 ) {
224183
) );
225184
}
226185

186+
/**
187+
* For a given query trash all matches.
188+
*
189+
* @since 3.4.0
190+
* @param array $query Query array to pass to wc_get_orders().
191+
* @return int Count of orders that were trashed.
192+
*/
193+
protected static function trash_orders_query( $query ) {
194+
$orders = wc_get_orders( $query );
195+
$count = 0;
196+
197+
if ( $orders ) {
198+
foreach ( $orders as $order ) {
199+
$order->delete( false );
200+
$count ++;
201+
}
202+
}
203+
204+
return $count;
205+
}
206+
227207
/**
228208
* Anonymize old completed orders.
229209
*
230210
* @since 3.4.0
231211
* @param int $limit Limit orders to process per batch.
232-
* @param int $page Page to process.
233212
* @return int Number of orders processed.
234213
*/
235-
public static function anonymize_completed_orders( $limit = 20, $page = 1 ) {
214+
public static function anonymize_completed_orders( $limit = 20 ) {
236215
$option = wc_parse_relative_date_option( get_option( 'woocommerce_anonymize_completed_orders' ) );
237216

238217
if ( empty( $option['number'] ) ) {
@@ -246,6 +225,90 @@ public static function anonymize_completed_orders( $limit = 20, $page = 1 ) {
246225
'anonymized' => false,
247226
) );
248227
}
228+
229+
/**
230+
* For a given query, anonymize all matches.
231+
*
232+
* @since 3.4.0
233+
* @param array $query Query array to pass to wc_get_orders().
234+
* @return int Count of orders that were anonymized.
235+
*/
236+
protected static function anonymize_orders_query( $query ) {
237+
$orders = wc_get_orders( $query );
238+
$count = 0;
239+
240+
if ( $orders ) {
241+
foreach ( $orders as $order ) {
242+
WC_Privacy_Erasers::remove_order_personal_data( $order );
243+
$count ++;
244+
}
245+
}
246+
247+
return $count;
248+
}
249+
250+
/**
251+
* Delete inactive accounts.
252+
*
253+
* @since 3.4.0
254+
* @param int $limit Limit users to process per batch.
255+
* @return int Number of users processed.
256+
*/
257+
public static function delete_inactive_accounts( $limit = 20 ) {
258+
$option = wc_parse_relative_date_option( get_option( 'woocommerce_delete_inactive_accounts' ) );
259+
260+
if ( empty( $option['number'] ) ) {
261+
return 0;
262+
}
263+
264+
return self::delete_inactive_accounts_query( strtotime( '-' . $option['number'] . ' ' . $option['unit'] ), $limit );
265+
}
266+
267+
/**
268+
* Delete inactive accounts.
269+
*
270+
* @since 3.4.0
271+
* @param int $timestamp Timestamp to delete customers before.
272+
* @param int $limit Limit number of users to delete per run.
273+
* @return int Count of customers that were deleted.
274+
*/
275+
protected static function delete_inactive_accounts_query( $timestamp, $limit = 20 ) {
276+
$count = 0;
277+
$user_query = new WP_User_Query( array(
278+
'fields' => 'ID',
279+
'number' => $limit,
280+
'role__in' => apply_filters( 'woocommerce_delete_inactive_account_roles', array(
281+
'Customer',
282+
'Subscriber',
283+
) ),
284+
'meta_query' => array(
285+
'relation' => 'AND',
286+
array(
287+
'key' => 'wc_last_active',
288+
'value' => (string) $timestamp,
289+
'compare' => '<',
290+
'type' => 'NUMERIC',
291+
),
292+
array(
293+
'key' => 'wc_last_active',
294+
'value' => '0',
295+
'compare' => '>',
296+
'type' => 'NUMERIC',
297+
),
298+
),
299+
) );
300+
301+
$user_ids = $user_query->get_results();
302+
303+
if ( $user_ids ) {
304+
foreach ( $user_ids as $user_id ) {
305+
wp_delete_user( $user_id );
306+
$count ++;
307+
}
308+
}
309+
310+
return $count;
311+
}
249312
}
250313

251314
new WC_Privacy();

Diff for: includes/data-stores/class-wc-order-data-store-cpt.php

+6-1
Original file line numberDiff line numberDiff line change
@@ -259,11 +259,16 @@ protected function update_post_meta( &$order ) {
259259
}
260260

261261
// If customer changed, update any downloadable permissions.
262-
if ( in_array( 'customer_user', $updated_props, true ) || in_array( 'billing_email', $updated_props, true ) ) {
262+
if ( in_array( 'customer_id', $updated_props, true ) || in_array( 'billing_email', $updated_props, true ) ) {
263263
$data_store = WC_Data_Store::load( 'customer-download' );
264264
$data_store->update_user_by_order_id( $id, $order->get_customer_id(), $order->get_billing_email() );
265265
}
266266

267+
// Mark user account as active.
268+
if ( in_array( 'customer_id', $updated_props, true ) ) {
269+
wc_update_user_last_active( $order->get_customer_id() );
270+
}
271+
267272
do_action( 'woocommerce_order_object_updated_props', $order, $updated_props );
268273
}
269274

0 commit comments

Comments
 (0)