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"); } }