> $recipients Recipients. * @param string $fallback_message SMS fallback message for Viber + SMS. */ public function create( int $author_id, string $scheme, string $message, array $recipients, string $fallback_message = '' ): int { global $wpdb; $tables = Installer::get_table_names(); $now = current_time( 'mysql' ); $uuid = wp_generate_uuid4(); $inserted = $wpdb->insert( $tables['campaigns'], array( 'uuid' => $uuid, 'author_id' => $author_id, 'scheme' => $scheme, 'message' => $message, 'fallback_message' => $fallback_message, 'recipients_total' => count( $recipients ), 'status' => 'created', 'created_at' => $now, 'updated_at' => $now, ), array( '%s', '%d', '%s', '%s', '%s', '%d', '%s', '%s', '%s' ) ); if ( false === $inserted ) { return 0; } $campaign_id = (int) $wpdb->insert_id; foreach ( $recipients as $recipient ) { $wpdb->insert( $tables['campaign_recipients'], array( 'campaign_id' => $campaign_id, 'user_id' => isset( $recipient['user_id'] ) ? absint( $recipient['user_id'] ) : null, 'order_id' => isset( $recipient['order_id'] ) ? absint( $recipient['order_id'] ) : null, 'phone' => isset( $recipient['phone'] ) ? (string) $recipient['phone'] : '', 'name' => isset( $recipient['name'] ) ? (string) $recipient['name'] : '', 'role' => isset( $recipient['role'] ) ? (string) $recipient['role'] : '', 'status' => 'created', 'created_at' => $now, 'updated_at' => $now, ), array( '%d', '%d', '%d', '%s', '%s', '%s', '%s', '%s', '%s' ) ); } return $campaign_id; } /** * Get campaign. * * @param int $campaign_id Campaign ID. * @return object|null */ public function get( int $campaign_id ) { global $wpdb; $tables = Installer::get_table_names(); return $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$tables['campaigns']} WHERE id = %d", $campaign_id ) ); } /** * Get pending recipients. * * @param int $campaign_id Campaign ID. * @param int $limit Limit. * @return array */ public function get_pending_recipients( int $campaign_id, int $limit ): array { global $wpdb; $tables = Installer::get_table_names(); return $wpdb->get_results( $wpdb->prepare( "SELECT * FROM {$tables['campaign_recipients']} WHERE campaign_id = %d AND status = 'created' ORDER BY id ASC LIMIT %d", $campaign_id, $limit ) ); } /** * Atomically claim a recipient for processing. * * @param int $recipient_id Recipient row ID. */ public function claim_recipient( int $recipient_id ): bool { global $wpdb; $tables = Installer::get_table_names(); $updated = $wpdb->update( $tables['campaign_recipients'], array( 'status' => 'processing', 'updated_at' => current_time( 'mysql' ), ), array( 'id' => $recipient_id, 'status' => 'created', ), array( '%s', '%s' ), array( '%d', '%s' ) ); return 1 === (int) $updated; } /** * Mark campaign status. * * @param int $campaign_id Campaign ID. * @param string $status Status. */ public function update_status( int $campaign_id, string $status ): bool { global $wpdb; $tables = Installer::get_table_names(); return false !== $wpdb->update( $tables['campaigns'], array( 'status' => $status, 'updated_at' => current_time( 'mysql' ), ), array( 'id' => $campaign_id ), array( '%s', '%s' ), array( '%d' ) ); } /** * Mark recipient processed. * * @param int $recipient_id Recipient row ID. * @param string $status Status. * @param string $channel Channel. * @param string $api_message_id API message ID. * @param string $api_response API response. * @param string $error_reason Error reason. */ public function update_recipient( int $recipient_id, string $status, string $channel, string $api_message_id = '', string $api_response = '', string $error_reason = '' ): bool { global $wpdb; $tables = Installer::get_table_names(); return false !== $wpdb->update( $tables['campaign_recipients'], array( 'channel' => $channel, 'status' => $status, 'api_message_id' => $api_message_id, 'api_response' => $api_response, 'error_reason' => $error_reason, 'processed_at' => current_time( 'mysql' ), 'updated_at' => current_time( 'mysql' ), ), array( 'id' => $recipient_id ), array( '%s', '%s', '%s', '%s', '%s', '%s', '%s' ), array( '%d' ) ); } /** * Recalculate campaign counters and final status. * * @param int $campaign_id Campaign ID. */ public function refresh_counters( int $campaign_id ): void { global $wpdb; $tables = Installer::get_table_names(); $row = $wpdb->get_row( $wpdb->prepare( "SELECT COUNT(*) AS total, SUM(CASE WHEN status IN ('sent', 'failed') THEN 1 ELSE 0 END) AS processed, SUM(CASE WHEN status = 'sent' THEN 1 ELSE 0 END) AS success_count, SUM(CASE WHEN status = 'failed' THEN 1 ELSE 0 END) AS error_count FROM {$tables['campaign_recipients']} WHERE campaign_id = %d", $campaign_id ) ); if ( ! $row ) { return; } $total = (int) $row->total; $processed = (int) $row->processed; $errors = (int) $row->error_count; $status = $processed >= $total ? ( $errors > 0 ? 'completed_with_errors' : 'completed' ) : 'processing'; $wpdb->update( $tables['campaigns'], array( 'recipients_total' => $total, 'processed_count' => $processed, 'success_count' => (int) $row->success_count, 'error_count' => $errors, 'status' => $status, 'updated_at' => current_time( 'mysql' ), ), array( 'id' => $campaign_id ), array( '%d', '%d', '%d', '%d', '%s', '%s' ), array( '%d' ) ); } /** * Get recent campaigns. * * @param int $limit Limit. * @return array */ public function get_recent( int $limit = 20 ): array { global $wpdb; $tables = Installer::get_table_names(); return $wpdb->get_results( $wpdb->prepare( "SELECT * FROM {$tables['campaigns']} ORDER BY created_at DESC, id DESC LIMIT %d", $limit ) ); } /** * Get campaigns that still have pending recipients. * * @param int $limit Limit. * @return array */ public function get_processable( int $limit = 10 ): array { global $wpdb; $tables = Installer::get_table_names(); return $wpdb->get_results( $wpdb->prepare( "SELECT c.* FROM {$tables['campaigns']} c WHERE c.status IN ('created', 'processing') AND EXISTS ( SELECT 1 FROM {$tables['campaign_recipients']} r WHERE r.campaign_id = c.id AND r.status = 'created' ) ORDER BY c.created_at ASC, c.id ASC LIMIT %d", $limit ) ); } /** * Get campaign recipients. * * @param int $campaign_id Campaign ID. * @return array */ public function get_recipients( int $campaign_id ): array { global $wpdb; $tables = Installer::get_table_names(); return $wpdb->get_results( $wpdb->prepare( "SELECT * FROM {$tables['campaign_recipients']} WHERE campaign_id = %d ORDER BY id ASC", $campaign_id ) ); } }