fix: resolve container width issues, spa redirects, and appearance settings overwrite. feat: enhance order/sub details and newsletter layout

This commit is contained in:
Dwindi Ramadhana
2026-02-05 00:09:40 +07:00
parent a0b5f8496d
commit 5f08c18ec7
77 changed files with 7027 additions and 4546 deletions

View File

@@ -0,0 +1,366 @@
<?php
/**
* Subscriber Table - Custom database table for newsletter subscribers
*
* Provides scalable storage for subscribers instead of wp_options
*
* @package WooNooW\Database
*/
namespace WooNooW\Database;
class SubscriberTable
{
const TABLE_NAME = 'woonoow_subscribers';
const DB_VERSION = '1.0';
const DB_VERSION_OPTION = 'woonoow_subscribers_db_version';
/**
* Get full table name with prefix
*/
public static function get_table_name()
{
global $wpdb;
return $wpdb->prefix . self::TABLE_NAME;
}
/**
* Create or update the subscribers table
*/
public static function create_table()
{
global $wpdb;
$table_name = self::get_table_name();
$charset_collate = $wpdb->get_charset_collate();
$sql = "CREATE TABLE $table_name (
id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
email VARCHAR(255) NOT NULL,
user_id BIGINT(20) UNSIGNED DEFAULT NULL,
status ENUM('pending','active','unsubscribed') DEFAULT 'pending',
consent TINYINT(1) DEFAULT 0,
consent_text TEXT,
source VARCHAR(50) DEFAULT 'form',
subscribed_at DATETIME DEFAULT NULL,
confirmed_at DATETIME DEFAULT NULL,
unsubscribed_at DATETIME DEFAULT NULL,
ip_address VARCHAR(45) DEFAULT NULL,
PRIMARY KEY (id),
UNIQUE KEY idx_email (email),
KEY idx_status (status),
KEY idx_user_id (user_id)
) $charset_collate;";
require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
dbDelta($sql);
update_option(self::DB_VERSION_OPTION, self::DB_VERSION);
}
/**
* Check if table exists
*/
public static function table_exists()
{
global $wpdb;
$table_name = self::get_table_name();
return $wpdb->get_var("SHOW TABLES LIKE '$table_name'") === $table_name;
}
/**
* Migrate existing subscribers from wp_options to custom table
*
* @return array Migration result with counts
*/
public static function migrate_from_options()
{
global $wpdb;
// Ensure table exists
if (!self::table_exists()) {
self::create_table();
}
$table_name = self::get_table_name();
$subscribers = get_option('woonoow_newsletter_subscribers', []);
if (empty($subscribers)) {
return ['migrated' => 0, 'skipped' => 0, 'message' => 'No subscribers to migrate'];
}
$migrated = 0;
$skipped = 0;
foreach ($subscribers as $sub) {
if (empty($sub['email'])) {
$skipped++;
continue;
}
// Check if already exists in table
$exists = $wpdb->get_var($wpdb->prepare(
"SELECT id FROM $table_name WHERE email = %s",
$sub['email']
));
if ($exists) {
$skipped++;
continue;
}
// Insert into new table
$result = $wpdb->insert($table_name, [
'email' => $sub['email'],
'user_id' => $sub['user_id'] ?? null,
'status' => $sub['status'] ?? 'active',
'consent' => !empty($sub['consent']) ? 1 : 0,
'subscribed_at' => $sub['subscribed_at'] ?? current_time('mysql'),
'confirmed_at' => $sub['confirmed_at'] ?? null,
'ip_address' => $sub['ip_address'] ?? null,
]);
if ($result) {
$migrated++;
} else {
$skipped++;
}
}
// If all migrated successfully, remove the option
if ($migrated > 0 && $skipped === 0) {
delete_option('woonoow_newsletter_subscribers');
}
return [
'migrated' => $migrated,
'skipped' => $skipped,
'message' => "Migrated $migrated subscribers, skipped $skipped",
];
}
// =========================================================================
// CRUD OPERATIONS
// =========================================================================
/**
* Add a new subscriber
*/
public static function add($data)
{
global $wpdb;
$table_name = self::get_table_name();
$result = $wpdb->insert($table_name, [
'email' => $data['email'],
'user_id' => $data['user_id'] ?? null,
'status' => $data['status'] ?? 'pending',
'consent' => !empty($data['consent']) ? 1 : 0,
'consent_text' => $data['consent_text'] ?? null,
'source' => $data['source'] ?? 'form',
'subscribed_at' => $data['subscribed_at'] ?? current_time('mysql'),
'ip_address' => $data['ip_address'] ?? null,
]);
if ($result) {
return $wpdb->insert_id;
}
return false;
}
/**
* Get a subscriber by email
*/
public static function get_by_email($email)
{
global $wpdb;
$table_name = self::get_table_name();
return $wpdb->get_row($wpdb->prepare(
"SELECT * FROM $table_name WHERE email = %s",
$email
), ARRAY_A);
}
/**
* Get a subscriber by ID
*/
public static function get($id)
{
global $wpdb;
$table_name = self::get_table_name();
return $wpdb->get_row($wpdb->prepare(
"SELECT * FROM $table_name WHERE id = %d",
$id
), ARRAY_A);
}
/**
* Update a subscriber
*/
public static function update($id, $data)
{
global $wpdb;
$table_name = self::get_table_name();
return $wpdb->update($table_name, $data, ['id' => $id]);
}
/**
* Update subscriber by email
*/
public static function update_by_email($email, $data)
{
global $wpdb;
$table_name = self::get_table_name();
return $wpdb->update($table_name, $data, ['email' => $email]);
}
/**
* Delete a subscriber
*/
public static function delete($id)
{
global $wpdb;
$table_name = self::get_table_name();
return $wpdb->delete($table_name, ['id' => $id]);
}
/**
* Delete subscriber by email
*/
public static function delete_by_email($email)
{
global $wpdb;
$table_name = self::get_table_name();
return $wpdb->delete($table_name, ['email' => $email]);
}
/**
* Get all active subscribers
*
* @param array $filters Optional filters
* @return array List of subscribers
*/
public static function get_active($filters = [])
{
global $wpdb;
$table_name = self::get_table_name();
$where = ["status = 'active'"];
$values = [];
// Filter: subscribed after date
if (!empty($filters['subscribed_after'])) {
$where[] = "subscribed_at >= %s";
$values[] = $filters['subscribed_after'];
}
// Filter: subscribed before date
if (!empty($filters['subscribed_before'])) {
$where[] = "subscribed_at <= %s";
$values[] = $filters['subscribed_before'];
}
// Filter: registered users only
if (!empty($filters['registered_only'])) {
$where[] = "user_id IS NOT NULL";
}
// Filter: guests only
if (!empty($filters['guests_only'])) {
$where[] = "user_id IS NULL";
}
$where_sql = implode(' AND ', $where);
$sql = "SELECT * FROM $table_name WHERE $where_sql ORDER BY subscribed_at DESC";
if (!empty($values)) {
$sql = $wpdb->prepare($sql, ...$values);
}
return $wpdb->get_results($sql, ARRAY_A);
}
/**
* Get all subscribers with pagination
*/
public static function get_all($args = [])
{
global $wpdb;
$table_name = self::get_table_name();
$defaults = [
'per_page' => 20,
'page' => 1,
'status' => '',
'search' => '',
'orderby' => 'subscribed_at',
'order' => 'DESC',
];
$args = wp_parse_args($args, $defaults);
$where = [];
$values = [];
if (!empty($args['status'])) {
$where[] = "status = %s";
$values[] = $args['status'];
}
if (!empty($args['search'])) {
$where[] = "email LIKE %s";
$values[] = '%' . $wpdb->esc_like($args['search']) . '%';
}
$where_sql = !empty($where) ? 'WHERE ' . implode(' AND ', $where) : '';
// Get total count
$count_sql = "SELECT COUNT(*) FROM $table_name $where_sql";
if (!empty($values)) {
$count_sql = $wpdb->prepare($count_sql, ...$values);
}
$total = (int) $wpdb->get_var($count_sql);
// Get paginated results
$offset = ($args['page'] - 1) * $args['per_page'];
$orderby = sanitize_sql_orderby($args['orderby'] . ' ' . $args['order']) ?: 'subscribed_at DESC';
$sql = "SELECT * FROM $table_name $where_sql ORDER BY $orderby LIMIT %d OFFSET %d";
$values[] = $args['per_page'];
$values[] = $offset;
$items = $wpdb->get_results($wpdb->prepare($sql, ...$values), ARRAY_A);
return [
'items' => $items,
'total' => $total,
'pages' => ceil($total / $args['per_page']),
];
}
/**
* Count subscribers by status
*/
public static function count_by_status($status = null)
{
global $wpdb;
$table_name = self::get_table_name();
if ($status) {
return (int) $wpdb->get_var($wpdb->prepare(
"SELECT COUNT(*) FROM $table_name WHERE status = %s",
$status
));
}
return (int) $wpdb->get_var("SELECT COUNT(*) FROM $table_name");
}
}