30, 'headers' => array( 'x-api-key' => $api_key, 'Accept' => 'application/json', 'Content-Type' => 'application/json', ), 'body' => wp_json_encode( $body ), ) ); if ( is_wp_error( $response ) ) { return self::result( false, $response->get_error_message(), null, 0 ); } $http_code = (int) wp_remote_retrieve_response_code( $response ); $raw_body = wp_remote_retrieve_body( $response ); $decoded = json_decode( $raw_body, true ); if ( 200 === $http_code && is_array( $decoded ) && ! empty( $decoded['status'] ) ) { $message = isset( $decoded['message'] ) ? (string) $decoded['message'] : __( 'Submission sent successfully.', 'abstract-one-crm-form-integration' ); return self::result( true, $message, $decoded['data'] ?? null, $http_code ); } $message = __( 'CRM rejected the submission.', 'abstract-one-crm-form-integration' ); if ( is_array( $decoded ) && ! empty( $decoded['message'] ) ) { $message = (string) $decoded['message']; } return self::result( false, $message, $decoded, $http_code ); } /** * Build API payload from workflow + submission values. * * @param array $workflow Workflow. * @param array $values Submitted values keyed by form field name. * @return array */ public static function build_payload( $workflow, $values ) { $mapping = $workflow['field_mapping'] ?? array(); $payload = array( 'projectID' => $workflow['project_id'] ?? '', 'campaignID' => $workflow['campaign_id'] ?? '', 'sub_source' => $workflow['sub_source'] ?? 'Organic', ); $all_fields = array_merge( AOLI_Workflow_Repository::crm_fields()['required'], AOLI_Workflow_Repository::crm_fields()['optional'] ); foreach ( array_keys( $all_fields ) as $crm_field ) { $form_field = $mapping[ $crm_field ] ?? ''; if ( '' === $form_field ) { continue; } $value = self::resolve_value( $form_field, $values ); if ( '' !== $value ) { $payload[ $crm_field ] = $value; } } // Auto-capture UTM from request when mapped to special token. $utm_keys = array( 'utm_id', 'utm_campaign', 'utm_source', 'utm_medium', 'utm_term' ); foreach ( $utm_keys as $utm_key ) { $form_field = $mapping[ $utm_key ] ?? ''; if ( '__utm_auto__' === $form_field && empty( $payload[ $utm_key ] ) ) { $payload[ $utm_key ] = self::get_utm_from_request( $utm_key ); } } return $payload; } /** * Resolve mapped field value. * * @param string $form_field Field key or special token. * @param array $values Submission values. * @return string */ private static function resolve_value( $form_field, $values ) { if ( '__utm_auto__' === $form_field ) { return ''; } if ( isset( $values[ $form_field ] ) ) { return is_array( $values[ $form_field ] ) ? sanitize_text_field( implode( ', ', $values[ $form_field ] ) ) : sanitize_text_field( (string) $values[ $form_field ] ); } return ''; } /** * Read UTM parameter from request/cookie. * * @param string $key UTM key. * @return string */ private static function get_utm_from_request( $key ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended if ( isset( $_GET[ $key ] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended return sanitize_text_field( wp_unslash( $_GET[ $key ] ) ); } $cookie_key = 'aoli_' . $key; if ( isset( $_COOKIE[ $cookie_key ] ) ) { return sanitize_text_field( wp_unslash( $_COOKIE[ $cookie_key ] ) ); } return ''; } /** * Sanitize payload before send. * * @param array $payload Raw payload. * @return array */ private static function sanitize_payload( $payload ) { $clean = array(); $string_fields = array( 'name', 'mobile_number', 'alternate_number', 'email', 'profession', 'location', 'notes', 'projectID', 'campaignID', 'sub_source', 'utm_id', 'utm_campaign', 'utm_source', 'utm_medium', 'utm_term', ); foreach ( $string_fields as $field ) { if ( isset( $payload[ $field ] ) && '' !== (string) $payload[ $field ] ) { $clean[ $field ] = sanitize_text_field( (string) $payload[ $field ] ); } } if ( isset( $clean['email'] ) ) { $clean['email'] = sanitize_email( $clean['email'] ); } if ( isset( $clean['mobile_number'] ) ) { $clean['mobile_number'] = self::normalize_mobile( $clean['mobile_number'] ); } if ( isset( $clean['alternate_number'] ) ) { $clean['alternate_number'] = self::normalize_mobile( $clean['alternate_number'] ); if ( $clean['alternate_number'] === $clean['mobile_number'] ) { unset( $clean['alternate_number'] ); } } return $clean; } /** * Validate required CRM fields. * * @param array $payload Payload. * @return string|null Error message. */ private static function validate_payload( $payload ) { $required = array( 'name', 'mobile_number', 'projectID', 'campaignID', 'sub_source' ); foreach ( $required as $field ) { if ( empty( $payload[ $field ] ) ) { return sprintf( /* translators: %s: field name */ __( 'Missing required field: %s', 'abstract-one-crm-form-integration' ), $field ); } } if ( ! self::is_valid_indian_mobile( $payload['mobile_number'] ) ) { return __( 'Mobile number must be a valid 10-digit Indian number.', 'abstract-one-crm-form-integration' ); } if ( ! empty( $payload['alternate_number'] ) && ! self::is_valid_indian_mobile( $payload['alternate_number'] ) ) { return __( 'Alternate number must be a valid 10-digit Indian number.', 'abstract-one-crm-form-integration' ); } if ( ! empty( $payload['email'] ) && ! is_email( $payload['email'] ) ) { return __( 'Email address is invalid.', 'abstract-one-crm-form-integration' ); } return null; } /** * Normalize mobile to last 10 digits. * * @param string $number Raw number. * @return string */ public static function normalize_mobile( $number ) { $digits = preg_replace( '/\D+/', '', (string) $number ); if ( strlen( $digits ) > 10 ) { $digits = substr( $digits, -10 ); } return $digits; } /** * Validate Indian mobile number. * * @param string $number Mobile number. * @return bool */ public static function is_valid_indian_mobile( $number ) { $number = self::normalize_mobile( $number ); return (bool) preg_match( '/^[6-9]\d{9}$/', $number ); } /** * Format result array. * * @param bool $success Success flag. * @param string $message Message. * @param mixed $data Response data. * @param int $code HTTP code. * @return array */ private static function result( $success, $message, $data, $code ) { return array( 'success' => (bool) $success, 'message' => (string) $message, 'data' => $data, 'http_code' => (int) $code, ); } }