367 lines
9.9 KiB
PHP
367 lines
9.9 KiB
PHP
<?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");
|
|
}
|
|
}
|