account_sid = $account_sid; $this->auth_token = $auth_token; } else { $secure = SecureOptions::get_instance(); $this->account_sid = $secure->get( 'twilio_account_sid', '' ) ?: null; $this->auth_token = $secure->get( 'twilio_auth_token', '' ) ?: null; } $this->from_number = $from_number ?? ( new TwilioSettings() )->get_from_number(); } /** * Check if client is configured. * * @return bool */ public function is_configured(): bool { return ! empty( $this->account_sid ) && ! empty( $this->auth_token ) && ! empty( $this->from_number ); } /** * Send an SMS message. * * @param string $to Recipient phone number (E.164 format). * @param string $body Message body. * @return array{success: bool, sid?: string, error?: string} */ public function sendSms( string $to, string $body ): array { if ( ! $this->is_configured() ) { Log::error( 'TwilioClient: not configured', array() ); return array( 'success' => false, 'error' => __( 'Twilio is not configured. Please add Account SID, Auth Token, and From number in settings.', 'afroit-messaging-workflows' ) ); } $to = $this->normalize_phone( $to ); $body = trim( $body ); if ( '' === $to || '' === $body ) { return array( 'success' => false, 'error' => __( 'Invalid recipient or message body.', 'afroit-messaging-workflows' ) ); } $url = self::API_BASE . '/Accounts/' . $this->account_sid . '/Messages.json'; $auth = base64_encode( $this->account_sid . ':' . $this->auth_token ); $response = wp_remote_post( $url, array( 'headers' => array( 'Authorization' => 'Basic ' . $auth, 'Content-Type' => 'application/x-www-form-urlencoded', ), 'body' => array( 'To' => $to, 'From' => $this->from_number, 'Body' => $body, ), 'timeout' => 25, ) ); if ( is_wp_error( $response ) ) { Log::error( 'TwilioClient: request failed', array( 'error' => $response->get_error_message() ) ); return array( 'success' => false, 'error' => $response->get_error_message() ); } $code = wp_remote_retrieve_response_code( $response ); $body_raw = wp_remote_retrieve_body( $response ); $data = json_decode( $body_raw, true ); if ( $code >= 200 && $code < 300 && ! empty( $data['sid'] ) ) { Log::info( 'TwilioClient: SMS sent', array( 'sid' => $data['sid'] ) ); return array( 'success' => true, 'sid' => $data['sid'] ); } $error_msg = isset( $data['message'] ) ? $data['message'] : ( isset( $data['error_message'] ) ? $data['error_message'] : __( 'Unknown Twilio error.', 'afroit-messaging-workflows' ) ); Log::error( 'TwilioClient: send failed', array( 'code' => $code, 'error' => $error_msg ) ); return array( 'success' => false, 'error' => $error_msg ); } /** * Normalize phone number to E.164 (basic). * * @param string $phone Raw phone. * @return string */ private function normalize_phone( string $phone ): string { $phone = preg_replace( '/[^\d+]/', '', $phone ); if ( '' !== $phone && '+' !== substr( $phone, 0, 1 ) ) { $phone = '+' . $phone; } return $phone; } }