get_option( 'phplist_url' ), '/' ); if ( empty( $base ) ) { return new WP_Error( 'woolist_no_url', __( 'phpList base URL is not configured.', 'woolist-phplist' ) ); } return $base . '/admin/?page=call&pi=restapi'; } /** * Execute a phpList REST API call. * * Credentials and all command parameters are sent in the POST body so * phpList can authenticate and parse the request correctly. * * @param string $cmd phpList API command (e.g. subscriberAdd). * @param array $extra Additional POST body parameters. * @return array|WP_Error Decoded JSON data or WP_Error. */ public function call( string $cmd, array $extra = [] ) { $url = $this->endpoint_url(); if ( is_wp_error( $url ) ) { WooList_Logger::error( 'Cannot build endpoint URL for cmd=' . $cmd . ': ' . $url->get_error_message() ); return $url; } // Build the POST body — credentials + command + any extra params. $body = array_merge( [ 'login' => (string) $this->get_option( 'phplist_login' ), 'password' => (string) $this->get_option( 'phplist_password' ), 'cmd' => $cmd, ], $extra ); // Log the outgoing request at debug level; redact the password. $logged_body = $body; $logged_body['password'] = '***'; WooList_Logger::debug( '→ API request cmd=' . $cmd . ' endpoint=' . $url . ' body=' . wp_json_encode( $logged_body ) ); $response = wp_remote_post( $url, [ 'timeout' => 15, 'body' => $body, ] ); if ( is_wp_error( $response ) ) { WooList_Logger::error( 'HTTP request failed cmd=' . $cmd . ' error=' . $response->get_error_message() ); return $response; } $code = wp_remote_retrieve_response_code( $response ); $body_raw = wp_remote_retrieve_body( $response ); // Always log the raw response at DEBUG so problems are immediately visible. WooList_Logger::debug( '← API response cmd=' . $cmd . ' http=' . $code . ' body=' . substr( $body_raw, 0, 2048 ) ); if ( $code < 200 || $code >= 300 ) { WooList_Logger::error( 'Non-2xx HTTP response cmd=' . $cmd . ' http=' . $code . ' body=' . substr( $body_raw, 0, 500 ) ); return new WP_Error( 'woolist_http_error', 'HTTP error ' . $code ); } $data = json_decode( $body_raw, true ); if ( json_last_error() !== JSON_ERROR_NONE ) { // Not JSON — likely an HTML error page or wrong endpoint. WooList_Logger::error( 'Non-JSON response cmd=' . $cmd . ' body=' . substr( $body_raw, 0, 500 ) ); return new WP_Error( 'woolist_json_error', 'phpList did not return JSON. Check endpoint URL and credentials.' ); } // phpList signals errors via status=error. // v3 nests the message inside data.message: {"status":"error","data":{"code":0,"message":"..."}} if ( isset( $data['status'] ) && strtolower( $data['status'] ) === 'error' ) { if ( is_array( $data['data'] ?? null ) ) { $message = $data['data']['message'] ?? $data['errormessage'] ?? $data['message'] ?? 'Unknown API error'; } else { $message = $data['errormessage'] ?? $data['message'] ?? $data['data'] ?? 'Unknown API error'; } $message = (string) $message; WooList_Logger::error( 'API error cmd=' . $cmd . ' message=' . $message . ' full_response=' . wp_json_encode( $data ) ); return new WP_Error( 'woolist_api_error', $message ); } WooList_Logger::debug( 'API call succeeded cmd=' . $cmd . ' response=' . wp_json_encode( $data ) ); return $data; } /** * Retrieve a subscriber by email address. * * @param string $email Subscriber email. * @return array|WP_Error */ public function subscriber_get_by_email( string $email ) { // No manual encoding — wp_remote_post encodes POST body values correctly. return $this->call( 'subscriberGetByEmail', [ 'email' => $email ] ); } /** * Add a new confirmed subscriber. * * @param string $email Subscriber email. * @return array|WP_Error */ public function subscriber_add( string $email ) { return $this->call( 'subscriberAdd', [ 'email' => $email, 'confirmed' => 1, 'htmlemail' => 1, ] ); } /** * Add a subscriber (by ID) to a phpList list. * * phpList REST API v3 accepts both "listid"/"subscriberid" (older) and * "list_id"/"subscriber_id" (some builds). We send all four so whichever * naming convention this installation uses will be satisfied. * * @param int $list_id phpList list ID. * @param int $subscriber_id phpList subscriber ID. * @return array|WP_Error */ public function list_subscriber_add( int $list_id, int $subscriber_id ) { WooList_Logger::debug( 'listSubscriberAdd params listid=' . $list_id . ' subscriberid=' . $subscriber_id ); return $this->call( 'listSubscriberAdd', [ 'listid' => $list_id, 'subscriberid' => $subscriber_id, 'list_id' => $list_id, // alternate param name used by some v3 builds 'subscriber_id' => $subscriber_id, // alternate param name ] ); } /** * Subscribe an email address to a phpList list. * * Looks up the subscriber first; creates one if not found; then adds * them to the target list. Returns a result array so callers can check * success without inspecting WP_Error internals. * * @param string $email Subscriber email address. * @param int $list_id phpList list ID. * @return array{success: bool, subscriber_id: int|null} */ public function subscribe_email_to_list( string $email, int $list_id ): array { WooList_Logger::debug( 'subscribe_email_to_list email=' . $email . ' list_id=' . $list_id ); // Step 1: look up existing subscriber. $subscriber_id = null; $existing = $this->subscriber_get_by_email( $email ); // phpList wraps subscriber data inside a "data" key: {"status":"success","data":{"id":…}} $existing_id = $existing['data']['id'] ?? $existing['id'] ?? null; if ( ! is_wp_error( $existing ) && ! empty( $existing_id ) ) { $subscriber_id = (int) $existing_id; WooList_Logger::debug( 'Found existing subscriber id=' . $subscriber_id . ' email=' . $email ); } else { // Step 2: create a new subscriber. WooList_Logger::debug( 'Subscriber not found, creating new email=' . $email ); $added = $this->subscriber_add( $email ); if ( is_wp_error( $added ) ) { WooList_Logger::error( 'Could not create subscriber email=' . $email . ' error=' . $added->get_error_message() ); return [ 'success' => false, 'subscriber_id' => null ]; } // phpList wraps the new subscriber inside a "data" key. $subscriber_id = isset( $added['data']['id'] ) ? (int) $added['data']['id'] : ( isset( $added['id'] ) ? (int) $added['id'] : null ); if ( $subscriber_id ) { WooList_Logger::info( 'Created new subscriber id=' . $subscriber_id . ' email=' . $email ); } else { WooList_Logger::error( 'API returned no subscriber ID after add email=' . $email . ' response=' . wp_json_encode( $added ) ); return [ 'success' => false, 'subscriber_id' => null ]; } } // Step 3: add subscriber to the list. WooList_Logger::debug( 'Adding subscriber ' . $subscriber_id . ' to list ' . $list_id ); $result = $this->list_subscriber_add( $list_id, $subscriber_id ); if ( is_wp_error( $result ) ) { WooList_Logger::error( 'Could not add subscriber to list subscriber_id=' . $subscriber_id . ' list_id=' . $list_id . ' error=' . $result->get_error_message() ); return [ 'success' => false, 'subscriber_id' => $subscriber_id ]; } WooList_Logger::info( 'Subscribed email=' . $email . ' subscriber_id=' . $subscriber_id . ' list_id=' . $list_id ); return [ 'success' => true, 'subscriber_id' => $subscriber_id ]; } /** * Retrieve all lists (used for connection testing). * * @return array|WP_Error */ public function lists_get() { return $this->call( 'listsGet' ); } }