Issues (850)

Security Analysis    4 potential vulnerabilities

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Code Injection (1)
Code Injection enables an attacker to execute arbitrary code on the server.
  Variable Injection (2)
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Cross-Site Scripting (1)
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  Header Injection
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

includes/class-wpinv-data-retention.php (2 issues)

1
<?php
2
/**
3
 * Data retention class.
4
 *
5
 * @package Invoicing
6
 * @since   2.8.22
7
 */
8
9
defined( 'ABSPATH' ) || exit;
10
11
/**
12
 * WPInv_Data_Retention Class.
13
 *
14
 * Handles user data anonymization and deletion.
15
 *
16
 * @since 2.8.22
17
 */
18
class WPInv_Data_Retention {
19
20
    /**
21
     * Error message.
22
     *
23
     * @var string
24
     */
25
    private $error_message;
26
27
    /**
28
     * Flag to control whether user deletion should be handled.
29
     *
30
     * @var bool
31
     */
32
    private $handle_user_deletion = true;
33
34
    /**
35
     * Class constructor.
36
     */
37
    public function __construct() {
38
        add_filter( 'wpinv_settings_misc', array( $this, 'add_data_retention_settings' ) );
39
40
        add_action( 'wpmu_delete_user', array( $this, 'maybe_handle_user_deletion' ), 1 );
41
        add_action( 'delete_user', array( $this, 'maybe_handle_user_deletion' ), 1 );
42
        add_filter( 'wp_privacy_personal_data_erasure_request', array( $this, 'handle_erasure_request' ), 10, 2 );
43
44
        add_action( 'getpaid_daily_maintenance', array( $this, 'perform_data_retention_cleanup' ) );
45
    }
46
47
    /**
48
     * Adds data retention settings to the misc settings page.
49
     *
50
     * @param array $misc_settings Existing misc settings.
51
     * @return array Updated misc settings.
52
     */
53
    public function add_data_retention_settings( $misc_settings ) {
54
        $misc_settings['data_retention'] = array(
55
            'id'   => 'data_retention',
56
            'name' => '<h3>' . __( 'Data Retention', 'invoicing' ) . '</h3>',
57
            'type' => 'header',
58
        );
59
60
        $misc_settings['data_retention_method'] = array(
61
            'id'      => 'data_retention_method',
62
            'name'    => __( 'Data Handling', 'invoicing' ),
63
            'desc'    => __( 'Choose how to handle user data when deletion is required.', 'invoicing' ),
64
            'type'    => 'select',
65
            'options' => array(
66
                'anonymize' => __( 'Anonymize data', 'invoicing' ),
67
                'delete'    => __( 'Delete data without anonymization', 'invoicing' ),
68
            ),
69
            'std'     => 'anonymize',
70
            'tooltip' => __( 'Anonymization replaces personal data with non-identifiable information. Direct deletion removes all data permanently.', 'invoicing' ),
71
        );
72
73
        $misc_settings['data_retention_period'] = array(
74
            'id'      => 'data_retention_period',
75
            'name'    => __( 'Retention Period', 'invoicing' ),
76
            'desc'    => __( 'Specify how long to retain customer data after processing.', 'invoicing' ),
77
            'type'    => 'select',
78
            'options' => array(
79
                'never' => __( 'Never delete (retain indefinitely)', 'invoicing' ),
80
                '30'    => __( '30 days', 'invoicing' ),
81
                '90'    => __( '90 days', 'invoicing' ),
82
                '180'   => __( '6 months', 'invoicing' ),
83
                '365'   => __( '1 year', 'invoicing' ),
84
                '730'   => __( '2 years', 'invoicing' ),
85
                '1825'  => __( '5 years', 'invoicing' ),
86
                '3650'  => __( '10 years', 'invoicing' ),
87
            ),
88
            'std'     => '3650',
89
            'tooltip' => __( 'Choose how long to keep processed customer data before final action. This helps balance data minimization with business needs.', 'invoicing' ),
90
        );
91
92
        return $misc_settings;
93
    }
94
95
    /**
96
     * Conditionally handles user deletion based on the flag.
97
     *
98
     * @param int $user_id The ID of the user being deleted.
99
     */
100
    public function maybe_handle_user_deletion( $user_id ) {
101
        if ( ! $this->handle_user_deletion ) {
102
            return;
103
        }
104
105
        if ( current_user_can( 'manage_options' ) ) {
106
            $this->handle_admin_user_deletion( $user_id );
107
        } else {
108
            $this->handle_self_account_deletion( $user_id );
109
        }
110
    }
111
112
    /**
113
     * Handles admin-initiated user deletion process.
114
     *
115
     * @since 2.8.22
116
     * @param int $user_id The ID of the user being deleted.
117
     */
118
    public function handle_admin_user_deletion( $user_id ) {
119
        if ( $this->has_active_subscriptions( $user_id ) ) {
120
            $this->prevent_user_deletion( $user_id, 'active_subscriptions' );
121
            return;
122
        }
123
124
        if ( $this->has_paid_invoices( $user_id ) ) {
125
            $retention_method = wpinv_get_option( 'data_retention_method', 'anonymize' );
126
            if ( 'anonymize' === $retention_method ) {
127
                $this->anonymize_user_data( $user_id );
128
                $this->prevent_user_deletion( $user_id, 'paid_invoices' );
129
            } else {
130
                $this->delete_user_data( $user_id );
131
            }
132
        }
133
    }
134
135
    /**
136
     * Handles user account self-deletion.
137
     *
138
     * @since 2.8.22
139
     * @param int $user_id The ID of the user being deleted.
140
     */
141
    public function handle_self_account_deletion( $user_id ) {
142
        $this->cancel_active_subscriptions( $user_id );
143
144
        if ( $this->has_paid_invoices( $user_id ) ) {
145
            $retention_method = wpinv_get_option( 'data_retention_method', 'anonymize' );
146
147
            if ( 'anonymize' === $retention_method ) {
148
                $user = get_userdata( $user_id );
149
150
                $this->anonymize_user_data( $user_id );
151
152
                $message = apply_filters( 'uwp_get_account_deletion_message', '', $user );
153
                do_action( 'uwp_send_account_deletion_emails', $user, $message );
154
155
                $this->end_user_session();
156
            }
157
        }
158
    }
159
160
    /**
161
     * Checks if user has active subscriptions.
162
     *
163
     * @since 2.8.22
164
     * @param int $user_id The ID of the user being checked.
165
     * @return bool True if user has active subscriptions, false otherwise.
166
     */
167
    private function has_active_subscriptions( $user_id ) {
168
        $subscriptions = getpaid_get_subscriptions(
169
            array(
170
                'customer_in' => array( (int) $user_id ),
171
                'status'      => 'active',
172
            )
173
        );
174
175
        return ! empty( $subscriptions );
176
    }
177
178
    /**
179
     * Cancels all active subscriptions for a user.
180
     *
181
     * @since 2.8.22
182
     * @param int $user_id The ID of the user.
183
     */
184
    private function cancel_active_subscriptions( $user_id ) {
185
        $subscriptions = getpaid_get_subscriptions(
186
            array(
187
                'customer_in' => array( (int) $user_id ),
188
                'status'      => 'active',
189
            )
190
        );
191
192
        foreach ( $subscriptions as $subscription ) {
193
            $subscription->cancel();
194
        }
195
    }
196
197
    /**
198
     * Checks if user has paid invoices.
199
     *
200
     * @since 2.8.22
201
     * @param int $user_id The ID of the user being checked.
202
     * @return bool True if user has paid invoices, false otherwise.
203
     */
204
    private function has_paid_invoices( $user_id ) {
205
        $invoices = wpinv_get_invoices(
206
            array(
207
                'user'   => (int) $user_id,
208
                'status' => 'publish',
209
            )
210
        );
211
212
        return ! empty( $invoices->total );
213
    }
214
215
    /**
216
     * Prevents user deletion by setting an error message and stopping execution.
217
     *
218
     * @since 2.8.22
219
     * @param int    $user_id The ID of the user being deleted.
220
     * @param string $reason  The reason for preventing deletion.
221
     */
222
    private function prevent_user_deletion( $user_id, $reason ) {
223
        $user = get_userdata( $user_id );
224
225
        if ( 'active_subscriptions' === $reason ) {
226
            $this->error_message = sprintf(
227
                /* translators: %s: user login */
228
                esc_html__( 'User deletion for %s has been halted. All active subscriptions should be cancelled first.', 'invoicing' ),
229
                $user->user_login
230
            );
231
        } else {
232
            $this->error_message = sprintf(
233
                /* translators: %s: user login */
234
                esc_html__( 'User deletion for %s has been halted due to paid invoices. Data will be anonymized instead.', 'invoicing' ),
235
                $user->user_login
236
            );
237
        }
238
239
        wp_die( $this->error_message, esc_html__( 'User Deletion Halted', 'invoicing' ), array( 'response' => 403 ) );
240
    }
241
242
    /**
243
     * Anonymizes user data.
244
     *
245
     * @since 2.8.22
246
     * @param int $user_id The ID of the user to anonymize.
247
     * @return bool True on success, false on failure.
248
     */
249
    private function anonymize_user_data( $user_id ) {
250
        global $wpdb;
251
252
        $user = get_userdata( $user_id );
253
        if ( ! $user ) {
254
            return false;
255
        }
256
257
        $table_name    = $wpdb->prefix . 'getpaid_customers';
258
        $deletion_date = gmdate( 'Y-m-d', strtotime( '+10 years' ) );
259
        $hashed_email  = $this->hash_email( $user->user_email );
260
261
        $updated = $wpdb->update(
262
            $table_name,
263
            array(
264
                'is_anonymized' => 1,
265
                'deletion_date' => $deletion_date,
266
                'email'         => $hashed_email,
267
                'email_cc'      => $hashed_email,
268
                'phone'         => '',
269
            ),
270
            array( 'user_id' => (int) $user->ID )
271
        );
272
273
        if ( false === $updated ) {
274
            return false;
275
        }
276
277
        wp_update_user(
278
            array(
279
                'ID'         => (int) $user->ID,
280
                'user_email' => $hashed_email,
281
            )
282
        );
283
284
        /**
285
         * Fires when anonymizing user meta fields.
286
         *
287
         * @since 2.8.22
288
         * @param int $user_id The ID of the user being anonymized.
289
         */
290
        do_action( 'wpinv_anonymize_user_meta_data', $user->ID );
291
292
        $user_meta_data = array(
293
            'nickname',
294
			'description',
295
			'rich_editing',
296
			'syntax_highlighting',
297
			'comment_shortcuts',
298
            'admin_color',
299
			'use_ssl',
300
			'show_admin_bar_front',
301
			'locale',
302
			'wp_capabilities',
303
            'wp_user_level',
304
			'dismissed_wp_pointers',
305
			'show_welcome_panel',
306
        );
307
308
        /**
309
         * Filters the user meta fields to be anonymized.
310
         *
311
         * @since 2.8.22
312
         * @param array $user_meta_data The meta fields to be anonymized.
313
         * @param int   $user_id          The ID of the user being anonymized.
314
         */
315
        $user_meta_data = apply_filters( 'wpinv_user_meta_data_to_anonymize', $user_meta_data, $user->ID );
316
317
        foreach ( $user_meta_data as $meta_key ) {
318
            delete_user_meta( $user->ID, $meta_key );
319
        }
320
321
        return $this->ensure_invoice_anonymization( $user->ID, 'anonymize' );
322
    }
323
324
    /**
325
     * Deletes user data without anonymization.
326
     *
327
     * @param int $user_id The ID of the user to delete.
328
     * @return bool True on success, false on failure.
329
     */
330
    private function delete_user_data( $user_id ) {
331
        // Delete associated invoices.
332
        $this->ensure_invoice_anonymization( $user_id, 'delete' );
333
334
        // Delete the user.
335
        if ( is_multisite() ) {
336
            wpmu_delete_user( $user_id );
337
        } else {
338
            wp_delete_user( $user_id );
339
        }
340
341
        /**
342
         * Fires after deleting user data without anonymization.
343
         *
344
         * @since 2.8.22
345
         * @param int $user_id The ID of the user being deleted.
346
         */
347
        do_action( 'wpinv_delete_user_data', $user_id );
348
349
        return true;
350
    }
351
352
    /**
353
     * Ensures invoice data remains anonymized.
354
     *
355
     * @since 2.8.22
356
     * @param int    $user_id The ID of the user whose invoices should be checked.
357
     * @param string $action  The action to perform (anonymize or delete).
358
     * @return bool True on success, false on failure.
359
     */
360
    public function ensure_invoice_anonymization( $user_id, $action = 'anonymize' ) {
361
        $invoices = wpinv_get_invoices( array( 'user' => $user_id ) );
362
363
        /**
364
         * Filters the invoice meta fields to be anonymized.
365
         *
366
         * @since 2.8.22
367
         * @param array $inv_meta_data The meta fields to be anonymized.
368
         * @param int   $user_id         The ID of the user being processed.
369
         */
370
        $inv_meta_data = apply_filters( 'wpinv_invoice_meta_data_to_anonymize', array(), $user_id );
371
372
        foreach ( $invoices->invoices as $invoice ) {
373
            foreach ( $inv_meta_data as $meta_key ) {
374
                delete_post_meta( $invoice->get_id(), $meta_key );
375
            }
376
377
            if ( 'anonymize' === $action ) {
378
                $hashed_inv_email    = $this->hash_email( $invoice->get_email() );
379
                $hashed_inv_email_cc = $this->hash_email( $invoice->get_email_cc() );
380
381
                $invoice->set_email( $hashed_inv_email );
382
                $invoice->set_email_cc( $hashed_inv_email_cc );
383
                $invoice->set_phone( '' );
384
                $invoice->set_ip( $this->anonymize_data( $invoice->get_ip() ) );
385
                $invoice->set_is_anonymized( 1 );
386
387
                /**
388
                 * Fires when anonymizing additional invoice data.
389
                 *
390
                 * @since 2.8.22
391
                 * @param WPInv_Invoice $invoice The invoice being anonymized.
392
                 * @param string        $action  The action being performed (anonymize or delete).
393
                 */
394
                do_action( 'wpinv_anonymize_invoice_data', $invoice, $action );
395
396
                $invoice->save();
397
            } else {
398
                $invoice->delete();
399
            }
400
        }
401
402
        return $this->log_deletion_action( $user_id, $invoices->invoices, $action );
403
    }
404
405
    /**
406
     * Logs the deletion or anonymization action for a user and their invoices.
407
     *
408
     * @since 2.8.22
409
     * @param int    $user_id  The ID of the user being processed.
410
     * @param array  $invoices An array of invoice objects being processed.
411
     * @param string $action   The action being performed (anonymize or delete).
412
     * @return bool True on success, false on failure.
413
     */
414
    private function log_deletion_action( $user_id, $invoices, $action ) {
415
        global $wpdb;
416
417
        $table_name = $wpdb->prefix . 'getpaid_anonymization_logs';
418
        $user_data  = get_userdata( $user_id );
419
420
        $additional_info = array(
421
            'Username'      => $user_data ? $user_data->user_login : 'N/A',
422
            'User Roles'    => $user_data ? implode(', ', $user_data->roles) : 'N/A',
423
            'Email'         => $user_data ? $user_data->user_email : 'N/A',
424
            'First Name'    => $user_data ? $user_data->first_name : 'N/A',
425
            'Last Name'     => $user_data ? $user_data->last_name : 'N/A',
426
            'Registered'    => $user_data ? $user_data->user_registered : 'N/A',
427
            'invoice_count' => count( $invoices ),
428
        );
429
430
431
        /**
432
         * Filters the additional info before logging.
433
         *
434
         * @since 2.8.22
435
         * @param array  $additional_info The additional information to be logged.
436
         * @param int    $user_id         The ID of the user being processed.
437
         * @param array  $invoices        The invoices being processed.
438
         * @param string $action          The action being performed (anonymize or delete).
439
         */
440
        $additional_info = apply_filters( 'wpinv_anonymization_log_additional_info', $additional_info, $user_id, $invoices, $action );
441
442
        $data = array(
443
            'user_id'         => $user_id,
444
            'action'          => sanitize_text_field( $action ),
445
            'data_type'       => 'User Invoices',
446
            'timestamp'       => current_time( 'mysql' ),
447
            'additional_info' => wp_json_encode( $additional_info ),
448
        );
449
450
        $format = array(
451
            '%d',  // user_id
452
            '%s',  // action
453
            '%s',  // data_type
454
            '%s',  // timestamp
455
            '%s',  // additional_info
456
        );
457
458
        if ( ! empty( $user_id ) && ! empty( $action ) ) {
459
            $result = $wpdb->update(
460
                $table_name,
461
                $data,
462
                array(
463
                    'user_id' => (int) $user_id,
464
                    'action'  => sanitize_text_field( $action ),
465
                ),
466
                $format,
467
                array( '%d', '%s' )
468
            );
469
470
            if ( false === $result ) {
471
                // If update fails, try to insert.
472
                $result = $wpdb->insert( $table_name, $data, $format );
473
            }
474
475
            if ( false === $result ) {
476
                wpinv_error_log( sprintf( 'Failed to log anonymization action for user ID: %d. Error: %s', $user_id, $wpdb->last_error ) );
477
                return false;
478
            }
479
        }
480
481
        /**
482
         * Fires after logging a deletion or anonymization action.
483
         *
484
         * @since 2.8.22
485
         * @param int    $user_id  The ID of the user being processed.
486
         * @param array  $invoices An array of invoice objects being processed.
487
         * @param string $action   The action being performed (anonymize or delete).
488
         * @param array  $data     The data that was inserted into the log.
489
         */
490
        do_action( 'wpinv_after_log_deletion_action', $user_id, $invoices, $action, $data );
491
492
        return true;
493
    }
494
495
    /**
496
     * Handles GDPR personal data erasure request.
497
     *
498
     * @since 2.8.22
499
     * @param array $response The default response.
500
     * @param int   $user_id  The ID of the user being erased.
501
     * @return array The modified response.
502
     */
503
    public function handle_erasure_request( $response, $user_id ) {
504
        if ( $this->has_active_subscriptions( $user_id ) ) {
505
            $response['messages'][]    = esc_html__( 'User has active subscriptions. Data cannot be erased at this time.', 'invoicing' );
506
            $response['items_removed'] = false;
507
        } elseif ( $this->has_paid_invoices( $user_id ) ) {
508
            $retention_method = wpinv_get_option( 'data_retention_method', 'anonymize' );
509
            if ( 'anonymize' === $retention_method ) {
510
                $this->anonymize_user_data( $user_id );
511
                $response['messages'][]     = esc_html__( 'User data has been anonymized due to existing paid invoices.', 'invoicing' );
512
                $response['items_removed']  = false;
513
                $response['items_retained'] = true;
514
            } else {
515
                $this->delete_user_data( $user_id );
516
                $response['messages'][]     = esc_html__( 'User data has been deleted.', 'invoicing' );
517
                $response['items_removed']  = true;
518
                $response['items_retained'] = false;
519
            }
520
        }
521
522
        return $response;
523
    }
524
525
    /**
526
     * Hashes email for anonymization.
527
     *
528
     * @since 2.8.22
529
     * @param string $email The email to hash.
530
     * @return string The hashed email.
531
     */
532
    private function hash_email( $email ) {
533
        $site_url = get_site_url();
534
        $domain   = wp_parse_url( $site_url, PHP_URL_HOST );
535
536
        if ( empty( $domain ) ) {
537
            return $email;
538
        }
539
540
        $clean_email     = sanitize_email( strtolower( trim( $email ) ) );
541
        $hash            = wp_hash( $clean_email );
542
        $hash            = substr( $hash, 0, 20 );
543
        $anonymized_email = sprintf( '%s@%s', $hash, $domain );
0 ignored issues
show
It seems like $domain can also be of type array; however, parameter $values of sprintf() does only seem to accept double|integer|string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

543
        $anonymized_email = sprintf( '%s@%s', $hash, /** @scrutinizer ignore-type */ $domain );
Loading history...
544
545
        /**
546
         * Filters the anonymized email before returning.
547
         *
548
         * @since 2.8.22
549
         * @param string $anonymized_email The anonymized email address.
550
         * @param string $email            The original email address.
551
         */
552
        return apply_filters( 'wpinv_anonymized_email', $anonymized_email, $email );
553
    }
554
555
    /**
556
     * Anonymizes a given piece of data.
557
     *
558
     * @since 2.8.22
559
     * @param string $data The data to anonymize.
560
     * @return string The anonymized data.
561
     */
562
    private function anonymize_data( $data ) {
563
        if ( empty( $data ) ) {
564
            return '';
565
        }
566
567
        return wp_privacy_anonymize_data( 'text', $data );
568
    }
569
570
    /**
571
     * Performs data retention cleanup.
572
     *
573
     * This method is responsible for cleaning up anonymized user data
574
     * that has exceeded the retention period.
575
     *
576
     * @since 2.8.22
577
     */
578
    public function perform_data_retention_cleanup() {
579
        global $wpdb;
580
581
        $retention_period = wpinv_get_option( 'data_retention_period', '3650' );
582
583
        // If retention period is set to 'never', exit the function.
584
        if ( 'never' === $retention_period ) {
585
            return;
586
        }
587
588
        $customers_table = $wpdb->prefix . 'getpaid_customers';
589
590
        // Calculate the cutoff date for data retention.
591
        $cutoff_date = gmdate( 'Y-m-d', strtotime( "-$retention_period days" ) );
592
593
        $expired_records = $wpdb->get_results(
594
            $wpdb->prepare(
595
                "SELECT * FROM $customers_table WHERE deletion_date < %s AND is_anonymized = 1",
596
                $cutoff_date
597
            )
598
        );
599
600
        /**
601
         * Fires before the data retention cleanup process begins.
602
         *
603
         * @since 2.8.22
604
         * @param array $expired_records Array of customer records to be processed.
605
         */
606
        do_action( 'getpaid_data_retention_before_cleanup', $expired_records );
607
608
        if ( ! empty( $expired_records ) ) {
609
            // Disable our custom user deletion handling.
610
            $this->handle_user_deletion = false;
611
612
            foreach ( $expired_records as $record ) {
613
                // Delete associated invoices.
614
                $this->ensure_invoice_anonymization( (int) $record->user_id, 'delete' );
615
616
                // Delete the user.
617
                wp_delete_user( (int) $record->user_id );
618
619
                /**
620
                 * Fires after processing each expired record during cleanup.
621
                 *
622
                 * @since 2.8.22
623
                 * @param object $record The customer record being processed.
624
                 */
625
                do_action( 'getpaid_data_retention_process_record', $record );
626
            }
627
628
            // Re-enable our custom user deletion handling.
629
            $this->handle_user_deletion = true;
630
631
            /**
632
             * Fires after the data retention cleanup process is complete.
633
             *
634
             * @since 2.8.22
635
             * @param array $expired_records Array of customer records that were processed.
636
             */
637
            do_action( 'getpaid_data_retention_after_cleanup', $expired_records );
638
        }
639
640
        /**
641
         * Fires after the data retention cleanup attempt, regardless of whether records were processed.
642
         *
643
         * @since 2.8.22
644
         * @param int $retention_period The current retention period in years.
645
         * @param string $cutoff_date The cutoff date used for identifying expired records.
646
         */
647
        do_action( 'getpaid_data_retention_cleanup_complete', $retention_period, $cutoff_date );
648
    }
649
650
    /**
651
     * Ends the user's current session.
652
     *
653
     * @since 2.8.22
654
     */
655
    private function end_user_session() {
656
        wp_logout();
657
658
        // Redirect after deletion.
659
        $redirect_page = home_url();
660
        wp_safe_redirect( $redirect_page );
661
        exit();
0 ignored issues
show
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
662
    }
663
}