Files
WooCleanup/includes/class-admin.php

593 lines
28 KiB
PHP
Raw Normal View History

<?php
/**
* WooCleanup Admin UI
*
* Adds two pages under the WooCommerce menu:
* 1. Account Cleanup table of flagged accounts with action buttons.
* 2. Cleanup Settings configurable options.
*/
defined( 'ABSPATH' ) || exit;
class WooCleanup_Admin {
/** @var WooCleanup */
private WooCleanup $plugin;
public function __construct( WooCleanup $plugin ) {
$this->plugin = $plugin;
add_action( 'admin_menu', [ $this, 'add_menu' ] );
add_action( 'admin_post_woo_cleanup_save_settings', [ $this, 'save_settings' ] );
add_action( 'admin_post_woo_cleanup_manual_run', [ $this, 'handle_manual_run' ] );
add_action( 'admin_post_woo_cleanup_send_notice', [ $this, 'handle_send_notice' ] );
add_action( 'admin_post_woo_cleanup_delete_user', [ $this, 'handle_delete_user' ] );
add_action( 'admin_notices', [ $this, 'show_admin_notices' ] );
}
// =========================================================================
// Menu Registration
// =========================================================================
public function add_menu(): void {
add_submenu_page(
'woocommerce',
__( 'Account Cleanup', 'woo-cleanup' ),
__( 'Account Cleanup', 'woo-cleanup' ),
'manage_woocommerce',
'woo-cleanup',
[ $this, 'render_accounts_page' ]
);
add_submenu_page(
'woocommerce',
__( 'Cleanup Settings', 'woo-cleanup' ),
__( 'Cleanup Settings', 'woo-cleanup' ),
'manage_woocommerce',
'woo-cleanup-settings',
[ $this, 'render_settings_page' ]
);
}
// =========================================================================
// Accounts List Page
// =========================================================================
public function render_accounts_page(): void {
if ( ! current_user_can( 'manage_woocommerce' ) ) {
wp_die( esc_html__( 'You do not have permission to view this page.', 'woo-cleanup' ) );
}
$options = $this->plugin->get_options();
$grace_days = absint( $options['grace_days'] );
$active_tab = isset( $_GET['tab'] ) && $_GET['tab'] === 'notice_sent'
? 'notice_sent'
: 'pending_notice';
$pending_users = WooCleanup::get_flagged_users( 'pending_notice' );
$noticed_users = WooCleanup::get_flagged_users( 'notice_sent' );
$last_run = get_option( 'woo_cleanup_last_run' );
// Cron next run
$next_run = wp_next_scheduled( WooCleanup::CRON_HOOK );
$users_to_show = $active_tab === 'notice_sent' ? $noticed_users : $pending_users;
?>
<div class="wrap">
<h1><?php esc_html_e( 'WooCleanup Inactive Account Manager', 'woo-cleanup' ); ?></h1>
<?php $this->output_admin_styles(); ?>
<!-- Summary bar -->
<div class="woo-cleanup-summary">
<div class="woo-cleanup-stat">
<span class="count"><?php echo count( $pending_users ); ?></span>
<span class="label"><?php esc_html_e( 'Awaiting Notice', 'woo-cleanup' ); ?></span>
</div>
<div class="woo-cleanup-stat woo-cleanup-stat--warning">
<span class="count"><?php echo count( $noticed_users ); ?></span>
<span class="label"><?php esc_html_e( 'In Grace Period', 'woo-cleanup' ); ?></span>
</div>
<div class="woo-cleanup-meta">
<?php if ( $last_run ) : ?>
<span><?php
printf(
/* translators: %s: human-readable time difference */
esc_html__( 'Last run: %s ago', 'woo-cleanup' ),
esc_html( human_time_diff( $last_run ) )
);
?></span>
<?php else : ?>
<span><?php esc_html_e( 'Cron has not run yet.', 'woo-cleanup' ); ?></span>
<?php endif; ?>
<?php if ( $next_run ) : ?>
&nbsp;|&nbsp;<span><?php
printf(
esc_html__( 'Next run: in %s', 'woo-cleanup' ),
esc_html( human_time_diff( $next_run ) )
);
?></span>
<?php endif; ?>
</div>
<!-- Manual run button -->
<form method="post" action="<?php echo esc_url( admin_url( 'admin-post.php' ) ); ?>"
style="margin-left:auto">
<?php wp_nonce_field( 'woo_cleanup_manual_run', '_wpnonce' ); ?>
<input type="hidden" name="action" value="woo_cleanup_manual_run">
<button type="submit" class="button button-secondary">
&#9654; <?php esc_html_e( 'Run Cleanup Now', 'woo-cleanup' ); ?>
</button>
</form>
</div>
<?php if ( empty( $options['enabled'] ) ) : ?>
<div class="notice notice-warning inline">
<p>
<?php esc_html_e( 'WooCleanup is currently disabled. Enable it on the ', 'woo-cleanup' ); ?>
<a href="<?php echo esc_url( admin_url( 'admin.php?page=woo-cleanup-settings' ) ); ?>">
<?php esc_html_e( 'Settings page', 'woo-cleanup' ); ?>
</a>.
</p>
</div>
<?php endif; ?>
<!-- Tabs -->
<h2 class="nav-tab-wrapper" style="margin-top:16px">
<a href="<?php echo esc_url( admin_url( 'admin.php?page=woo-cleanup&tab=pending_notice' ) ); ?>"
class="nav-tab <?php echo $active_tab === 'pending_notice' ? 'nav-tab-active' : ''; ?>">
<?php esc_html_e( 'Awaiting Notice', 'woo-cleanup' ); ?>
<span class="woo-cleanup-badge"><?php echo count( $pending_users ); ?></span>
</a>
<a href="<?php echo esc_url( admin_url( 'admin.php?page=woo-cleanup&tab=notice_sent' ) ); ?>"
class="nav-tab <?php echo $active_tab === 'notice_sent' ? 'nav-tab-active' : ''; ?>">
<?php esc_html_e( 'Grace Period (Pending Deletion)', 'woo-cleanup' ); ?>
<span class="woo-cleanup-badge woo-cleanup-badge--warning"><?php echo count( $noticed_users ); ?></span>
</a>
</h2>
<?php if ( empty( $users_to_show ) ) : ?>
<div class="woo-cleanup-empty">
<?php if ( ! $last_run ) : ?>
<p><?php esc_html_e( 'No flagged accounts yet. The daily cron has not run. Click "Run Cleanup Now" to scan immediately.', 'woo-cleanup' ); ?></p>
<?php else : ?>
<p><?php esc_html_e( 'No accounts in this category.', 'woo-cleanup' ); ?></p>
<?php endif; ?>
</div>
<?php else : ?>
<table class="wp-list-table widefat fixed striped woo-cleanup-table">
<thead>
<tr>
<th><?php esc_html_e( 'User', 'woo-cleanup' ); ?></th>
<th><?php esc_html_e( 'Email', 'woo-cleanup' ); ?></th>
<th><?php esc_html_e( 'Registered', 'woo-cleanup' ); ?></th>
<th><?php esc_html_e( 'Last Order', 'woo-cleanup' ); ?></th>
<?php if ( $active_tab === 'pending_notice' ) : ?>
<th><?php esc_html_e( 'Inactive Since', 'woo-cleanup' ); ?></th>
<?php else : ?>
<th><?php esc_html_e( 'Notice Sent', 'woo-cleanup' ); ?></th>
<th><?php esc_html_e( 'Deletes On', 'woo-cleanup' ); ?></th>
<th><?php esc_html_e( 'Days Left', 'woo-cleanup' ); ?></th>
<?php endif; ?>
<th><?php esc_html_e( 'Actions', 'woo-cleanup' ); ?></th>
</tr>
</thead>
<tbody>
<?php foreach ( $users_to_show as $user ) :
$user_id = (int) $user->ID;
$inactive_since = (int) get_user_meta( $user_id, WooCleanup::META_INACTIVE_SINCE, true );
$notice_ts = (int) get_user_meta( $user_id, WooCleanup::META_NOTICE_SENT, true );
$delete_ts = $notice_ts + ( $grace_days * DAY_IN_SECONDS );
$days_left = max( 0, (int) ceil( ( $delete_ts - time() ) / DAY_IN_SECONDS ) );
$last_order = WooCleanup::get_last_order_date( $user_id );
$edit_url = get_edit_user_link( $user_id );
$row_class = ( $active_tab === 'notice_sent' && $days_left <= 2 ) ? 'woo-cleanup-row--urgent' : '';
?>
<tr class="<?php echo esc_attr( $row_class ); ?>">
<td>
<a href="<?php echo esc_url( $edit_url ); ?>" target="_blank">
<?php echo esc_html( $user->display_name ); ?>
</a>
<br><small style="color:#888">#<?php echo $user_id; ?></small>
</td>
<td><?php echo esc_html( $user->user_email ); ?></td>
<td><?php echo esc_html( date_i18n( get_option( 'date_format' ), strtotime( $user->user_registered ) ) ); ?></td>
<td><?php echo $last_order ? esc_html( $last_order ) : '<em style="color:#aaa">' . esc_html__( 'Never', 'woo-cleanup' ) . '</em>'; ?></td>
<?php if ( $active_tab === 'pending_notice' ) : ?>
<td><?php echo $inactive_since ? esc_html( date_i18n( get_option( 'date_format' ), $inactive_since ) ) : '—'; ?></td>
<?php else : ?>
<td><?php echo $notice_ts ? esc_html( date_i18n( get_option( 'date_format' ), $notice_ts ) ) : '—'; ?></td>
<td><?php echo $notice_ts ? esc_html( date_i18n( get_option( 'date_format' ), $delete_ts ) ) : '—'; ?></td>
<td>
<?php if ( $days_left <= 1 ) : ?>
<span class="woo-cleanup-days-urgent"><?php echo $days_left === 0 ? esc_html__( 'Today', 'woo-cleanup' ) : esc_html( $days_left . ' ' . __( 'day', 'woo-cleanup' ) ); ?></span>
<?php else : ?>
<?php echo esc_html( $days_left . ' ' . __( 'days', 'woo-cleanup' ) ); ?>
<?php endif; ?>
</td>
<?php endif; ?>
<td class="woo-cleanup-actions">
<?php if ( $active_tab === 'pending_notice' ) : ?>
<!-- Send notice now -->
<form method="post" action="<?php echo esc_url( admin_url( 'admin-post.php' ) ); ?>" style="display:inline">
<?php wp_nonce_field( 'woo_cleanup_send_notice_' . $user_id, '_wpnonce' ); ?>
<input type="hidden" name="action" value="woo_cleanup_send_notice">
<input type="hidden" name="user_id" value="<?php echo $user_id; ?>">
<button type="submit" class="button button-small">
<?php esc_html_e( 'Send Notice', 'woo-cleanup' ); ?>
</button>
</form>
<?php endif; ?>
<!-- Delete now -->
<form method="post" action="<?php echo esc_url( admin_url( 'admin-post.php' ) ); ?>"
style="display:inline"
onsubmit="return confirm('<?php echo esc_js( sprintf( __( 'Permanently delete %s? This cannot be undone.', 'woo-cleanup' ), $user->display_name ) ); ?>')">
<?php wp_nonce_field( 'woo_cleanup_delete_user_' . $user_id, '_wpnonce' ); ?>
<input type="hidden" name="action" value="woo_cleanup_delete_user">
<input type="hidden" name="user_id" value="<?php echo $user_id; ?>">
<button type="submit" class="button button-small woo-cleanup-btn-delete">
<?php esc_html_e( 'Delete Now', 'woo-cleanup' ); ?>
</button>
</form>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<?php endif; ?>
</div><!-- .wrap -->
<?php
}
// =========================================================================
// Settings Page
// =========================================================================
public function render_settings_page(): void {
if ( ! current_user_can( 'manage_woocommerce' ) ) {
wp_die( esc_html__( 'You do not have permission to view this page.', 'woo-cleanup' ) );
}
$options = $this->plugin->get_options();
?>
<div class="wrap">
<h1><?php esc_html_e( 'WooCleanup Settings', 'woo-cleanup' ); ?></h1>
<form method="post" action="<?php echo esc_url( admin_url( 'admin-post.php' ) ); ?>">
<?php wp_nonce_field( 'woo_cleanup_save_settings', '_wpnonce' ); ?>
<input type="hidden" name="action" value="woo_cleanup_save_settings">
<table class="form-table" role="presentation">
<!-- Enable / Disable -->
<tr>
<th scope="row"><?php esc_html_e( 'Enable Cleanup', 'woo-cleanup' ); ?></th>
<td>
<label>
<input type="checkbox" name="woo_cleanup[enabled]" value="1"
<?php checked( 1, (int) $options['enabled'] ); ?>>
<?php esc_html_e( 'Run the daily cleanup cron', 'woo-cleanup' ); ?>
</label>
</td>
</tr>
<!-- Inactivity Period -->
<tr>
<th scope="row">
<label for="wc_inactivity_months">
<?php esc_html_e( 'Inactivity Period (months)', 'woo-cleanup' ); ?>
</label>
</th>
<td>
<input type="number" id="wc_inactivity_months"
name="woo_cleanup[inactivity_months]"
value="<?php echo esc_attr( $options['inactivity_months'] ); ?>"
min="1" max="120" style="width:80px">
<p class="description">
<?php esc_html_e( 'Customers with no completed/processing order in this many months will be flagged. Default: 18.', 'woo-cleanup' ); ?>
</p>
</td>
</tr>
<!-- Grace Period -->
<tr>
<th scope="row">
<label for="wc_grace_days">
<?php esc_html_e( 'Grace Period (days)', 'woo-cleanup' ); ?>
</label>
</th>
<td>
<input type="number" id="wc_grace_days"
name="woo_cleanup[grace_days]"
value="<?php echo esc_attr( $options['grace_days'] ); ?>"
min="1" max="365" style="width:80px">
<p class="description">
<?php esc_html_e( 'Days between the notice email and account deletion. Default: 7.', 'woo-cleanup' ); ?>
</p>
</td>
</tr>
<!-- Email Subject -->
<tr>
<th scope="row">
<label for="wc_email_subject">
<?php esc_html_e( 'Notice Email Subject', 'woo-cleanup' ); ?>
</label>
</th>
<td>
<input type="text" id="wc_email_subject"
name="woo_cleanup[email_subject]"
value="<?php echo esc_attr( $options['email_subject'] ); ?>"
class="large-text">
<p class="description">
<?php esc_html_e( 'Available placeholders: {site_name}, {username}, {email}, {deletion_date}, {grace_days}', 'woo-cleanup' ); ?>
</p>
</td>
</tr>
<!-- Email Body -->
<tr>
<th scope="row">
<label for="wc_email_body">
<?php esc_html_e( 'Notice Email Body', 'woo-cleanup' ); ?>
</label>
</th>
<td>
<textarea id="wc_email_body"
name="woo_cleanup[email_body]"
rows="12" class="large-text"><?php echo esc_textarea( $options['email_body'] ); ?></textarea>
<p class="description">
<?php esc_html_e( 'Plain text. Available placeholders: {site_name}, {username}, {email}, {deletion_date}, {grace_days}. HTML is supported.', 'woo-cleanup' ); ?>
</p>
</td>
</tr>
</table>
<h2><?php esc_html_e( 'Preview Placeholders', 'woo-cleanup' ); ?></h2>
<table class="widefat" style="max-width:500px">
<thead><tr><th><?php esc_html_e( 'Placeholder', 'woo-cleanup' ); ?></th><th><?php esc_html_e( 'Replaced With', 'woo-cleanup' ); ?></th></tr></thead>
<tbody>
<tr><td><code>{site_name}</code></td><td><?php echo esc_html( get_bloginfo( 'name' ) ); ?></td></tr>
<tr><td><code>{username}</code></td><td><?php esc_html_e( "Customer's display name", 'woo-cleanup' ); ?></td></tr>
<tr><td><code>{email}</code></td><td><?php esc_html_e( "Customer's email address", 'woo-cleanup' ); ?></td></tr>
<tr><td><code>{deletion_date}</code></td><td><?php esc_html_e( 'Calculated deletion date', 'woo-cleanup' ); ?></td></tr>
<tr><td><code>{grace_days}</code></td><td><?php echo esc_html( $options['grace_days'] ); ?></td></tr>
</tbody>
</table>
<p style="margin-top:20px">
<?php submit_button( __( 'Save Settings', 'woo-cleanup' ), 'primary', 'submit', false ); ?>
&nbsp;
<a href="<?php echo esc_url( admin_url( 'admin.php?page=woo-cleanup' ) ); ?>" class="button button-secondary">
<?php esc_html_e( 'View Flagged Accounts', 'woo-cleanup' ); ?>
</a>
</p>
</form>
</div>
<?php
}
// =========================================================================
// Form Handlers (admin-post.php targets)
// =========================================================================
public function save_settings(): void {
if ( ! current_user_can( 'manage_woocommerce' )
|| ! check_admin_referer( 'woo_cleanup_save_settings' ) ) {
wp_die( esc_html__( 'Security check failed.', 'woo-cleanup' ) );
}
$raw = isset( $_POST['woo_cleanup'] ) ? (array) $_POST['woo_cleanup'] : [];
$options = [
'enabled' => ! empty( $raw['enabled'] ) ? 1 : 0,
'inactivity_months' => max( 1, absint( $raw['inactivity_months'] ?? 18 ) ),
'grace_days' => max( 1, absint( $raw['grace_days'] ?? 7 ) ),
'email_subject' => sanitize_text_field( $raw['email_subject'] ?? '' ),
'email_body' => wp_kses_post( $raw['email_body'] ?? '' ),
];
update_option( 'woo_cleanup_options', $options );
wp_safe_redirect( add_query_arg(
[ 'page' => 'woo-cleanup-settings', 'message' => 'saved' ],
admin_url( 'admin.php' )
) );
exit;
}
public function handle_manual_run(): void {
if ( ! current_user_can( 'manage_woocommerce' )
|| ! check_admin_referer( 'woo_cleanup_manual_run' ) ) {
wp_die( esc_html__( 'Security check failed.', 'woo-cleanup' ) );
}
$this->plugin->run_cleanup();
wp_safe_redirect( add_query_arg(
[ 'page' => 'woo-cleanup', 'message' => 'ran' ],
admin_url( 'admin.php' )
) );
exit;
}
public function handle_send_notice(): void {
if ( ! current_user_can( 'manage_woocommerce' ) ) {
wp_die( esc_html__( 'Security check failed.', 'woo-cleanup' ) );
}
$user_id = absint( $_POST['user_id'] ?? 0 );
if ( ! $user_id || ! check_admin_referer( 'woo_cleanup_send_notice_' . $user_id ) ) {
wp_die( esc_html__( 'Security check failed.', 'woo-cleanup' ) );
}
$options = $this->plugin->get_options();
$this->plugin->send_notice( $user_id, absint( $options['grace_days'] ), $options );
update_user_meta( $user_id, WooCleanup::META_NOTICE_SENT, time() );
wp_safe_redirect( add_query_arg(
[ 'page' => 'woo-cleanup', 'message' => 'noticed', 'uid' => $user_id ],
admin_url( 'admin.php' )
) );
exit;
}
public function handle_delete_user(): void {
if ( ! current_user_can( 'manage_woocommerce' ) ) {
wp_die( esc_html__( 'Security check failed.', 'woo-cleanup' ) );
}
$user_id = absint( $_POST['user_id'] ?? 0 );
if ( ! $user_id || ! check_admin_referer( 'woo_cleanup_delete_user_' . $user_id ) ) {
wp_die( esc_html__( 'Security check failed.', 'woo-cleanup' ) );
}
// Double-check: never delete admins via this form.
$user = get_userdata( $user_id );
if ( $user && in_array( 'administrator', (array) $user->roles, true ) ) {
wp_die( esc_html__( 'Cannot delete administrator accounts.', 'woo-cleanup' ) );
}
$this->plugin->delete_customer( $user_id );
wp_safe_redirect( add_query_arg(
[ 'page' => 'woo-cleanup', 'message' => 'deleted' ],
admin_url( 'admin.php' )
) );
exit;
}
// =========================================================================
// Admin Notices
// =========================================================================
public function show_admin_notices(): void {
$screen = get_current_screen();
if ( ! $screen || strpos( $screen->id, 'woo-cleanup' ) === false ) {
return;
}
$message = $_GET['message'] ?? '';
$uid = absint( $_GET['uid'] ?? 0 );
switch ( $message ) {
case 'saved':
echo '<div class="notice notice-success is-dismissible"><p>'
. esc_html__( 'Settings saved.', 'woo-cleanup' )
. '</p></div>';
break;
case 'ran':
echo '<div class="notice notice-success is-dismissible"><p>'
. esc_html__( 'Cleanup routine completed. The account list has been updated.', 'woo-cleanup' )
. '</p></div>';
break;
case 'noticed':
$user = $uid ? get_userdata( $uid ) : null;
echo '<div class="notice notice-success is-dismissible"><p>'
. esc_html( sprintf(
__( 'Notice email sent to %s.', 'woo-cleanup' ),
$user ? $user->display_name : "user #{$uid}"
) )
. '</p></div>';
break;
case 'deleted':
echo '<div class="notice notice-success is-dismissible"><p>'
. esc_html__( 'Account deleted.', 'woo-cleanup' )
. '</p></div>';
break;
}
}
// =========================================================================
// Inline Styles (small footprint; avoids an extra HTTP request)
// =========================================================================
private function output_admin_styles(): void {
?>
<style>
.woo-cleanup-summary {
display: flex;
align-items: center;
gap: 24px;
background: #fff;
border: 1px solid #e2e4e7;
border-radius: 4px;
padding: 16px 20px;
margin: 16px 0;
}
.woo-cleanup-stat {
text-align: center;
}
.woo-cleanup-stat .count {
display: block;
font-size: 2em;
font-weight: 700;
line-height: 1;
color: #2271b1;
}
.woo-cleanup-stat--warning .count {
color: #d63638;
}
.woo-cleanup-stat .label {
display: block;
font-size: 0.8em;
color: #646970;
margin-top: 4px;
}
.woo-cleanup-meta {
font-size: 0.85em;
color: #646970;
}
.woo-cleanup-badge {
display: inline-block;
background: #2271b1;
color: #fff;
border-radius: 10px;
padding: 1px 7px;
font-size: 0.8em;
margin-left: 4px;
vertical-align: middle;
}
.woo-cleanup-badge--warning {
background: #d63638;
}
.woo-cleanup-table th,
.woo-cleanup-table td {
vertical-align: middle;
}
.woo-cleanup-actions {
white-space: nowrap;
}
.woo-cleanup-btn-delete {
color: #d63638 !important;
border-color: #d63638 !important;
}
.woo-cleanup-btn-delete:hover {
background: #d63638 !important;
color: #fff !important;
}
.woo-cleanup-row--urgent td {
background-color: #fff5f5 !important;
}
.woo-cleanup-days-urgent {
color: #d63638;
font-weight: 700;
}
.woo-cleanup-empty {
background: #fff;
border: 1px solid #e2e4e7;
border-radius: 4px;
padding: 40px;
text-align: center;
color: #646970;
margin-top: 16px;
}
</style>
<?php
}
}