tigerstyle-whiskers/includes/whiskers/class-data-deletion.php
Ryan Malloy adbdae19c8 Initial commit: TigerStyle Whiskers v1.0.0
Navigate privacy laws with feline precision — detect every boundary,
respect every territory! GDPR compliance and privacy protection for
WordPress.

- Cookie consent management
- Privacy boundary detection
- GDPR-compliant analytics gating
- Cross-plugin consent coordination (integrates with TigerStyle Heat)
- Visitor preference tracking
- Configurable cookie categories

Includes build.sh and .distignore for WordPress-installable release ZIPs.
2026-05-27 14:31:51 -06:00

944 lines
32 KiB
PHP

<?php
/**
* Data Deletion Whisker for TigerStyle Whiskers
*
* Data deletion with surgical precision - like a cat covering its tracks
* Implements GDPR Right to Erasure and data retention policies
*/
// Prevent direct access
if (!defined('ABSPATH')) {
exit;
}
class TigerStyleWhiskers_DataDeletion {
/**
* Single instance
*/
private static $instance = null;
/**
* Deletion request statuses
*/
private $deletion_statuses = array(
'pending' => 'Pending Review',
'processing' => 'Processing Deletion',
'completed' => 'Deletion Completed',
'rejected' => 'Request Rejected',
'verified' => 'Identity Verified'
);
/**
* Data sources that can be deleted
*/
private $deletable_sources = array(
'user_account' => array(
'name' => 'User Account Data',
'description' => 'Profile, preferences, and account information',
'complexity' => 'medium',
'dependencies' => array('comments', 'orders')
),
'comments' => array(
'name' => 'Comments and Reviews',
'description' => 'All comments and review content',
'complexity' => 'low',
'dependencies' => array()
),
'orders' => array(
'name' => 'Order History',
'description' => 'E-commerce orders and transactions',
'complexity' => 'high',
'dependencies' => array('accounting_records')
),
'analytics' => array(
'name' => 'Analytics Data',
'description' => 'Tracking and behavior data',
'complexity' => 'medium',
'dependencies' => array()
),
'marketing' => array(
'name' => 'Marketing Data',
'description' => 'Email lists and marketing preferences',
'complexity' => 'low',
'dependencies' => array()
),
'logs' => array(
'name' => 'Access Logs',
'description' => 'Login and activity logs',
'complexity' => 'low',
'dependencies' => array()
)
);
/**
* Get instance
*/
public static function instance() {
if (is_null(self::$instance)) {
self::$instance = new self();
}
return self::$instance;
}
/**
* Constructor
*/
private function __construct() {
$this->init_deletion_whisker();
}
/**
* Initialize data deletion whisker
*/
private function init_deletion_whisker() {
// Frontend hooks for deletion requests
add_action('wp_ajax_tigerstyle_whiskers_request_deletion', array($this, 'handle_deletion_request'));
add_action('wp_ajax_nopriv_tigerstyle_whiskers_request_deletion', array($this, 'handle_deletion_request'));
// Admin hooks for managing deletion requests
if (is_admin()) {
add_action('admin_post_process_deletion_request', array($this, 'admin_process_deletion'));
add_action('admin_post_export_user_data', array($this, 'export_user_data'));
}
// Automated deletion hooks
add_action('tigerstyle_whiskers_daily_cleanup', array($this, 'process_automated_deletions'));
add_action('tigerstyle_whiskers_retention_cleanup', array($this, 'enforce_retention_policies'));
// Schedule automated cleanup if not already scheduled
if (!wp_next_scheduled('tigerstyle_whiskers_daily_cleanup')) {
wp_schedule_event(time(), 'daily', 'tigerstyle_whiskers_daily_cleanup');
}
// User deletion hooks
add_action('delete_user', array($this, 'handle_user_deletion'));
add_action('wp_privacy_personal_data_erased', array($this, 'handle_privacy_erasure'));
// Integration with WordPress privacy tools
add_filter('wp_privacy_personal_data_erasers', array($this, 'register_privacy_erasers'));
add_filter('wp_privacy_personal_data_exporters', array($this, 'register_privacy_exporters'));
if (defined('WP_DEBUG') && WP_DEBUG) {
error_log('TigerStyle Whiskers: Data deletion whisker is ready to cover tracks with precision!');
}
}
/**
* Handle deletion request from frontend
*/
public function handle_deletion_request() {
// Verify nonce
if (!wp_verify_nonce($_POST['nonce'], 'tigerstyle_whiskers_deletion')) {
wp_send_json_error(__('Security check failed', 'tigerstyle-whiskers'));
}
$email = sanitize_email($_POST['email'] ?? '');
$request_type = sanitize_text_field($_POST['request_type'] ?? 'full_deletion');
$reason = sanitize_textarea_field($_POST['reason'] ?? '');
$data_sources = array_map('sanitize_text_field', $_POST['data_sources'] ?? array());
if (empty($email) || !is_email($email)) {
wp_send_json_error(__('Please provide a valid email address', 'tigerstyle-whiskers'));
}
// Create deletion request
$request_id = $this->create_deletion_request(array(
'email' => $email,
'request_type' => $request_type,
'reason' => $reason,
'data_sources' => $data_sources,
'ip_address' => $this->get_user_ip(),
'user_agent' => $_SERVER['HTTP_USER_AGENT'] ?? '',
'status' => 'pending',
'created_at' => current_time('mysql')
));
if ($request_id) {
// Send confirmation email
$this->send_deletion_confirmation_email($email, $request_id);
// Notify administrators
$this->notify_admin_of_deletion_request($request_id);
wp_send_json_success(array(
'message' => __('Your deletion request has been received. You will receive a confirmation email shortly.', 'tigerstyle-whiskers'),
'request_id' => $request_id
));
} else {
wp_send_json_error(__('Failed to process deletion request. Please try again.', 'tigerstyle-whiskers'));
}
}
/**
* Create deletion request record
*/
private function create_deletion_request($request_data) {
global $wpdb;
// Create custom table if it doesn't exist
$this->create_deletion_requests_table();
$table_name = $wpdb->prefix . 'tigerstyle_whiskers_deletion_requests';
$result = $wpdb->insert(
$table_name,
array(
'email' => $request_data['email'],
'request_type' => $request_data['request_type'],
'reason' => $request_data['reason'],
'data_sources' => json_encode($request_data['data_sources']),
'ip_address_hash' => hash('sha256', $request_data['ip_address']),
'user_agent_hash' => hash('sha256', $request_data['user_agent']),
'status' => $request_data['status'],
'created_at' => $request_data['created_at'],
'verification_token' => wp_generate_password(32, false)
),
array('%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s')
);
return $result ? $wpdb->insert_id : false;
}
/**
* Create deletion requests table
*/
private function create_deletion_requests_table() {
global $wpdb;
$table_name = $wpdb->prefix . 'tigerstyle_whiskers_deletion_requests';
$charset_collate = $wpdb->get_charset_collate();
$sql = "CREATE TABLE IF NOT EXISTS $table_name (
id mediumint(9) NOT NULL AUTO_INCREMENT,
email varchar(255) NOT NULL,
request_type varchar(50) NOT NULL,
reason text,
data_sources text,
ip_address_hash varchar(64),
user_agent_hash varchar(64),
status varchar(20) DEFAULT 'pending',
verification_token varchar(32),
verified_at datetime DEFAULT NULL,
processed_at datetime DEFAULT NULL,
completed_at datetime DEFAULT NULL,
created_at datetime DEFAULT CURRENT_TIMESTAMP,
notes text,
PRIMARY KEY (id),
KEY email (email),
KEY status (status),
KEY created_at (created_at)
) $charset_collate;";
require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
dbDelta($sql);
}
/**
* Send deletion confirmation email
*/
private function send_deletion_confirmation_email($email, $request_id) {
$request = $this->get_deletion_request($request_id);
if (!$request) return;
$verification_url = add_query_arg(array(
'action' => 'verify_deletion',
'request_id' => $request_id,
'token' => $request->verification_token
), home_url());
$subject = sprintf(__('[%s] Data Deletion Request Confirmation', 'tigerstyle-whiskers'), get_bloginfo('name'));
$message = sprintf(__('Hello,
We have received a request to delete your personal data from %s.
Request Details:
- Request ID: %s
- Request Type: %s
- Date: %s
To verify this request and proceed with deletion, please click the following link:
%s
If you did not make this request, please ignore this email or contact us immediately.
This verification link will expire in 48 hours for security reasons.
Best regards,
The %s Team
---
This is an automated message from TigerStyle Whiskers GDPR Compliance System.', 'tigerstyle-whiskers'),
get_bloginfo('name'),
$request_id,
ucwords(str_replace('_', ' ', $request->request_type)),
date_i18n(get_option('date_format'), strtotime($request->created_at)),
$verification_url,
get_bloginfo('name')
);
$headers = array('Content-Type: text/plain; charset=UTF-8');
wp_mail($email, $subject, $message, $headers);
}
/**
* Notify admin of deletion request
*/
private function notify_admin_of_deletion_request($request_id) {
$admin_email = get_option('admin_email');
$request = $this->get_deletion_request($request_id);
if (!$request) return;
$subject = sprintf(__('[%s] New Data Deletion Request #%s', 'tigerstyle-whiskers'), get_bloginfo('name'), $request_id);
$admin_url = admin_url('admin.php?page=tigerstyle-whiskers&tab=deletion&request_id=' . $request_id);
$message = sprintf(__('A new data deletion request has been submitted.
Request Details:
- Request ID: %s
- Email: %s
- Request Type: %s
- Reason: %s
- Date: %s
Please review this request in the admin panel:
%s
The request requires verification by the user before processing can begin.
---
TigerStyle Whiskers GDPR Compliance System', 'tigerstyle-whiskers'),
$request_id,
$request->email,
ucwords(str_replace('_', ' ', $request->request_type)),
$request->reason ?: 'Not specified',
date_i18n(get_option('date_format'), strtotime($request->created_at)),
$admin_url
);
wp_mail($admin_email, $subject, $message);
}
/**
* Get deletion request by ID
*/
private function get_deletion_request($request_id) {
global $wpdb;
$table_name = $wpdb->prefix . 'tigerstyle_whiskers_deletion_requests';
return $wpdb->get_row($wpdb->prepare(
"SELECT * FROM $table_name WHERE id = %d",
$request_id
));
}
/**
* Process deletion request (admin action)
*/
public function admin_process_deletion() {
if (!current_user_can('manage_options')) {
wp_die(__('You do not have sufficient permissions', 'tigerstyle-whiskers'));
}
if (!wp_verify_nonce($_POST['deletion_nonce'], 'process_deletion')) {
wp_die(__('Security check failed', 'tigerstyle-whiskers'));
}
$request_id = intval($_POST['request_id']);
$action = sanitize_text_field($_POST['deletion_action']);
$notes = sanitize_textarea_field($_POST['admin_notes'] ?? '');
$request = $this->get_deletion_request($request_id);
if (!$request) {
wp_die(__('Invalid deletion request', 'tigerstyle-whiskers'));
}
switch ($action) {
case 'approve':
$this->execute_deletion($request_id, $notes);
break;
case 'reject':
$this->reject_deletion($request_id, $notes);
break;
case 'verify':
$this->verify_deletion_request($request_id);
break;
}
wp_redirect(admin_url('admin.php?page=tigerstyle-whiskers&tab=deletion&message=request_processed'));
exit;
}
/**
* Execute deletion with surgical precision
*/
private function execute_deletion($request_id, $admin_notes = '') {
$request = $this->get_deletion_request($request_id);
if (!$request || $request->status !== 'verified') {
return false;
}
// Update status to processing
$this->update_deletion_request_status($request_id, 'processing', $admin_notes);
$data_sources = json_decode($request->data_sources, true) ?: array();
$deletion_log = array();
// Process each data source with feline precision
foreach ($data_sources as $source) {
$result = $this->delete_data_source($request->email, $source);
$deletion_log[$source] = $result;
}
// If full deletion requested, delete everything
if ($request->request_type === 'full_deletion') {
foreach ($this->deletable_sources as $source => $config) {
if (!in_array($source, $data_sources)) {
$result = $this->delete_data_source($request->email, $source);
$deletion_log[$source] = $result;
}
}
}
// Store deletion log
$this->store_deletion_log($request_id, $deletion_log);
// Update status to completed
$this->update_deletion_request_status($request_id, 'completed', 'Deletion completed successfully');
// Send completion notification
$this->send_deletion_completion_email($request->email, $request_id, $deletion_log);
// Log for audit trail
TigerStyleWhiskers_AuditTrail::log_event(array(
'event_type' => 'data_deletion_completed',
'request_id' => $request_id,
'email_hash' => hash('sha256', $request->email),
'data_sources_deleted' => array_keys($deletion_log),
'admin_notes' => $admin_notes,
'timestamp' => current_time('timestamp')
));
return true;
}
/**
* Delete specific data source
*/
private function delete_data_source($email, $source) {
$result = array(
'source' => $source,
'success' => false,
'items_deleted' => 0,
'errors' => array(),
'timestamp' => current_time('mysql')
);
try {
switch ($source) {
case 'user_account':
$result = $this->delete_user_account_data($email);
break;
case 'comments':
$result = $this->delete_comment_data($email);
break;
case 'orders':
$result = $this->delete_order_data($email);
break;
case 'analytics':
$result = $this->delete_analytics_data($email);
break;
case 'marketing':
$result = $this->delete_marketing_data($email);
break;
case 'logs':
$result = $this->delete_log_data($email);
break;
default:
$result['errors'][] = 'Unknown data source: ' . $source;
}
} catch (Exception $e) {
$result['errors'][] = $e->getMessage();
}
return $result;
}
/**
* Delete user account data
*/
private function delete_user_account_data($email) {
$user = get_user_by('email', $email);
$result = array(
'source' => 'user_account',
'success' => false,
'items_deleted' => 0,
'errors' => array()
);
if (!$user) {
$result['errors'][] = 'User not found';
return $result;
}
// Before deleting, handle dependencies
$this->anonymize_user_content($user->ID);
// Delete user account
$deleted = wp_delete_user($user->ID);
if ($deleted) {
$result['success'] = true;
$result['items_deleted'] = 1;
} else {
$result['errors'][] = 'Failed to delete user account';
}
return $result;
}
/**
* Delete comment data
*/
private function delete_comment_data($email) {
$comments = get_comments(array('author_email' => $email));
$result = array(
'source' => 'comments',
'success' => true,
'items_deleted' => 0,
'errors' => array()
);
foreach ($comments as $comment) {
$deleted = wp_delete_comment($comment->comment_ID, true);
if ($deleted) {
$result['items_deleted']++;
} else {
$result['errors'][] = 'Failed to delete comment ID: ' . $comment->comment_ID;
}
}
return $result;
}
/**
* Delete order data (WooCommerce)
*/
private function delete_order_data($email) {
$result = array(
'source' => 'orders',
'success' => true,
'items_deleted' => 0,
'errors' => array()
);
if (!class_exists('WooCommerce')) {
$result['errors'][] = 'WooCommerce not active';
return $result;
}
// Get orders by email
$orders = wc_get_orders(array(
'billing_email' => $email,
'limit' => -1
));
foreach ($orders as $order) {
// Check if order can be deleted (accounting requirements)
if ($this->can_delete_order($order)) {
$order->delete(true);
$result['items_deleted']++;
} else {
// Anonymize instead of delete
$this->anonymize_order($order);
$result['items_deleted']++;
}
}
return $result;
}
/**
* Delete analytics data
*/
private function delete_analytics_data($email) {
// This would integrate with Google Analytics Data Deletion API
// For now, we'll remove local analytics data
$result = array(
'source' => 'analytics',
'success' => true,
'items_deleted' => 0,
'errors' => array()
);
// Remove from local analytics logs
$logs = get_option('tigerstyle_whiskers_processing_log', array());
$original_count = count($logs);
$logs = array_filter($logs, function($log) use ($email) {
$email_hash = hash('sha256', $email);
return !isset($log['email_hash']) || $log['email_hash'] !== $email_hash;
});
update_option('tigerstyle_whiskers_processing_log', $logs);
$result['items_deleted'] = $original_count - count($logs);
// Note: Real implementation would also request deletion from Google Analytics
// via their User Deletion API
return $result;
}
/**
* Delete marketing data
*/
private function delete_marketing_data($email) {
$result = array(
'source' => 'marketing',
'success' => true,
'items_deleted' => 0,
'errors' => array()
);
// Remove from newsletter subscriptions
// This would integrate with MailChimp, ConvertKit, etc.
return $result;
}
/**
* Delete log data
*/
private function delete_log_data($email) {
$result = array(
'source' => 'logs',
'success' => true,
'items_deleted' => 0,
'errors' => array()
);
// Remove from access logs and audit trails
$email_hash = hash('sha256', $email);
// Clean up audit trail
if (class_exists('TigerStyleWhiskers_AuditTrail')) {
$deleted = TigerStyleWhiskers_AuditTrail::delete_user_events($email_hash);
$result['items_deleted'] = $deleted;
}
return $result;
}
/**
* Anonymize user content instead of deleting (for dependencies)
*/
private function anonymize_user_content($user_id) {
// Anonymize comments
global $wpdb;
$wpdb->update(
$wpdb->comments,
array(
'comment_author' => 'Anonymous',
'comment_author_email' => 'deleted@privacy.local',
'comment_author_url' => '',
'comment_author_IP' => '0.0.0.0'
),
array('user_id' => $user_id),
array('%s', '%s', '%s', '%s'),
array('%d')
);
// Anonymize posts if necessary
$wpdb->update(
$wpdb->posts,
array('post_author' => 0),
array('post_author' => $user_id),
array('%d'),
array('%d')
);
}
/**
* Check if order can be deleted (accounting requirements)
*/
private function can_delete_order($order) {
// Orders must be kept for 7 years for accounting in most jurisdictions
$order_date = $order->get_date_created();
$seven_years_ago = new DateTime('-7 years');
return $order_date < $seven_years_ago;
}
/**
* Anonymize order instead of deleting
*/
private function anonymize_order($order) {
$order->set_billing_first_name('Anonymous');
$order->set_billing_last_name('Customer');
$order->set_billing_email('deleted@privacy.local');
$order->set_billing_phone('');
$order->set_billing_address_1('Deleted');
$order->set_billing_address_2('');
$order->set_billing_city('Privacy');
$order->set_billing_postcode('00000');
$order->set_shipping_first_name('Anonymous');
$order->set_shipping_last_name('Customer');
$order->set_shipping_address_1('Deleted');
$order->set_shipping_address_2('');
$order->set_shipping_city('Privacy');
$order->set_shipping_postcode('00000');
$order->save();
}
/**
* Store deletion log
*/
private function store_deletion_log($request_id, $deletion_log) {
global $wpdb;
$table_name = $wpdb->prefix . 'tigerstyle_whiskers_deletion_requests';
$wpdb->update(
$table_name,
array('deletion_log' => json_encode($deletion_log)),
array('id' => $request_id),
array('%s'),
array('%d')
);
}
/**
* Update deletion request status
*/
private function update_deletion_request_status($request_id, $status, $notes = '') {
global $wpdb;
$table_name = $wpdb->prefix . 'tigerstyle_whiskers_deletion_requests';
$update_data = array('status' => $status);
$update_format = array('%s');
if (!empty($notes)) {
$update_data['notes'] = $notes;
$update_format[] = '%s';
}
switch ($status) {
case 'processing':
$update_data['processed_at'] = current_time('mysql');
$update_format[] = '%s';
break;
case 'completed':
$update_data['completed_at'] = current_time('mysql');
$update_format[] = '%s';
break;
case 'verified':
$update_data['verified_at'] = current_time('mysql');
$update_format[] = '%s';
break;
}
$wpdb->update(
$table_name,
$update_data,
array('id' => $request_id),
$update_format,
array('%d')
);
}
/**
* Send deletion completion email
*/
private function send_deletion_completion_email($email, $request_id, $deletion_log) {
$subject = sprintf(__('[%s] Data Deletion Completed', 'tigerstyle-whiskers'), get_bloginfo('name'));
$deleted_sources = array();
$total_items = 0;
foreach ($deletion_log as $source => $result) {
if ($result['success']) {
$deleted_sources[] = ucwords(str_replace('_', ' ', $source)) . ' (' . $result['items_deleted'] . ' items)';
$total_items += $result['items_deleted'];
}
}
$message = sprintf(__('Hello,
Your data deletion request #%s has been completed successfully.
Data Sources Processed:
%s
Total Items Deleted: %s
Your personal data has been removed from our systems with feline precision. Some anonymized data may remain for legal or security purposes as outlined in our privacy policy.
If you have any questions about this deletion, please contact us.
Best regards,
The %s Team
---
TigerStyle Whiskers GDPR Compliance System', 'tigerstyle-whiskers'),
$request_id,
implode("\n", $deleted_sources),
$total_items,
get_bloginfo('name')
);
wp_mail($email, $subject, $message);
}
/**
* Get user IP (anonymized)
*/
private function get_user_ip() {
$ip = '';
if (!empty($_SERVER['HTTP_CLIENT_IP'])) {
$ip = $_SERVER['HTTP_CLIENT_IP'];
} elseif (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
$ip = $_SERVER['HTTP_X_FORWARDED_FOR'];
} elseif (!empty($_SERVER['REMOTE_ADDR'])) {
$ip = $_SERVER['REMOTE_ADDR'];
}
// Anonymize IP
if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
$ip_parts = explode('.', $ip);
$ip_parts[3] = '0';
$ip = implode('.', $ip_parts);
}
return $ip;
}
/**
* Register privacy erasers for WordPress privacy tools
*/
public function register_privacy_erasers($erasers) {
$erasers['tigerstyle-whiskers'] = array(
'eraser_friendly_name' => __('TigerStyle Whiskers Data', 'tigerstyle-whiskers'),
'callback' => array($this, 'privacy_eraser_callback'),
);
return $erasers;
}
/**
* Privacy eraser callback
*/
public function privacy_eraser_callback($email_address, $page = 1) {
$response = array(
'items_removed' => 0,
'items_retained' => 0,
'messages' => array(),
'done' => true,
);
// Erase TigerStyle Whiskers specific data
$logs = get_option('tigerstyle_whiskers_processing_log', array());
$email_hash = hash('sha256', $email_address);
$original_count = count($logs);
$logs = array_filter($logs, function($log) use ($email_hash) {
return !isset($log['email_hash']) || $log['email_hash'] !== $email_hash;
});
update_option('tigerstyle_whiskers_processing_log', $logs);
$items_removed = $original_count - count($logs);
$response['items_removed'] = $items_removed;
if ($items_removed > 0) {
$response['messages'][] = sprintf(
__('Removed %d TigerStyle Whiskers processing log entries.', 'tigerstyle-whiskers'),
$items_removed
);
}
return $response;
}
/**
* Render admin page for deletion management
*/
public function render_admin_page() {
?>
<div class="tigerstyle-whiskers-admin-section">
<h2><?php _e('Data Deletion & Right to Erasure', 'tigerstyle-whiskers'); ?></h2>
<p class="description">
<?php _e('Manage data deletion requests with surgical precision - like a cat covering its tracks completely.', 'tigerstyle-whiskers'); ?>
</p>
<?php $this->render_deletion_requests_table(); ?>
</div>
<?php
}
/**
* Render deletion requests table
*/
private function render_deletion_requests_table() {
global $wpdb;
$table_name = $wpdb->prefix . 'tigerstyle_whiskers_deletion_requests';
$requests = $wpdb->get_results("SELECT * FROM $table_name ORDER BY created_at DESC LIMIT 50");
?>
<div class="tw-deletion-requests">
<h3><?php _e('Recent Deletion Requests', 'tigerstyle-whiskers'); ?></h3>
<?php if (!empty($requests)): ?>
<table class="wp-list-table widefat fixed striped">
<thead>
<tr>
<th><?php _e('ID', 'tigerstyle-whiskers'); ?></th>
<th><?php _e('Email', 'tigerstyle-whiskers'); ?></th>
<th><?php _e('Type', 'tigerstyle-whiskers'); ?></th>
<th><?php _e('Status', 'tigerstyle-whiskers'); ?></th>
<th><?php _e('Created', 'tigerstyle-whiskers'); ?></th>
<th><?php _e('Actions', 'tigerstyle-whiskers'); ?></th>
</tr>
</thead>
<tbody>
<?php foreach ($requests as $request): ?>
<tr>
<td><?php echo esc_html($request->id); ?></td>
<td><?php echo esc_html($request->email); ?></td>
<td><?php echo esc_html(ucwords(str_replace('_', ' ', $request->request_type))); ?></td>
<td>
<span class="tw-status-<?php echo esc_attr($request->status); ?>">
<?php echo esc_html($this->deletion_statuses[$request->status] ?? $request->status); ?>
</span>
</td>
<td><?php echo esc_html(date_i18n(get_option('date_format'), strtotime($request->created_at))); ?></td>
<td>
<a href="<?php echo admin_url('admin.php?page=tigerstyle-whiskers&tab=deletion&action=view&request_id=' . $request->id); ?>"
class="button button-small">
<?php _e('View', 'tigerstyle-whiskers'); ?>
</a>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<?php else: ?>
<div class="notice notice-info">
<p><?php _e('No deletion requests found. The whiskers are keeping a clean house!', 'tigerstyle-whiskers'); ?></p>
</div>
<?php endif; ?>
</div>
<?php
}
}