| @@ -4,9 +4,9 @@ | ||
| 4 | 4 | * |
| 5 | 5 | * Routes (under `suredonation/v1`): |
| 6 | 6 | * - GET /onboarding/get-status — return { completed: 'yes'|'no' } |
| 7 | 7 | * - POST /onboarding/set-status — write completion + optional analytics |
| 8 | - * - POST /onboarding/create-campaign — create a published suredonation_cmpgn | |
| 8 | + * - POST /onboarding/create-campaign — create a draft suredonation_cmpgn | |
| 9 | 9 | * - POST /onboarding/user-details — persist lead capture (free-only step) |
| 10 | 10 | * |
| 11 | 11 | * @package SureDonation |
| 12 | 12 | */ |
| @@ -70,40 +70,15 @@ | ||
| 70 | 70 | ]; |
| 71 | 71 | } |
| 72 | 72 | |
| 73 | 73 | /** |
| 74 | - * Permission gate. Write requests (POST/PUT/PATCH/DELETE) additionally | |
| 75 | - * require a valid wp_rest nonce, matching Donors_API — onboarding forwards a | |
| 76 | - * lead to the BSF CRM, so the write boundary is pinned explicitly. | |
| 74 | + * Permission gate. | |
| 77 | 75 | * |
| 78 | - * @param \WP_REST_Request<array<string,mixed>>|null $request Current request. | |
| 79 | - * @return bool|\WP_Error | |
| 76 | + * @return bool | |
| 80 | 77 | * @since 1.0.0 |
| 81 | 78 | */ |
| 82 | - public function check_permissions( $request = null ) { | |
| 83 | - if ( ! current_user_can( 'manage_options' ) ) { | |
| 84 | - return false; | |
| 85 | - } | |
| 86 | - | |
| 87 | - if ( $request instanceof \WP_REST_Request ) { | |
| 88 | - $method = strtoupper( $request->get_method() ); | |
| 89 | - if ( in_array( $method, [ 'POST', 'PUT', 'PATCH', 'DELETE' ], true ) ) { | |
| 90 | - $nonce = $request->get_header( 'X-WP-Nonce' ); | |
| 91 | - if ( empty( $nonce ) ) { | |
| 92 | - $nonce_param = $request->get_param( '_wpnonce' ); | |
| 93 | - $nonce = is_string( $nonce_param ) ? $nonce_param : ''; | |
| 94 | - } | |
| 95 | - if ( empty( $nonce ) || ! wp_verify_nonce( $nonce, 'wp_rest' ) ) { | |
| 96 | - return new \WP_Error( | |
| 97 | - 'rest_forbidden', | |
| 98 | - __( 'Invalid or missing nonce.', 'suredonation' ), | |
| 99 | - [ 'status' => 403 ] | |
| 100 | - ); | |
| 101 | - } | |
| 102 | - } | |
| 103 | - } | |
| 104 | - | |
| 105 | - return true; | |
| 79 | + public function check_permissions() { | |
| 80 | + return current_user_can( 'manage_options' ); | |
| 106 | 81 | } |
| 107 | 82 | |
| 108 | 83 | /** |
| 109 | 84 | * GET /onboarding/get-status. |
| @@ -135,9 +110,9 @@ | ||
| 135 | 110 | |
| 136 | 111 | /** |
| 137 | 112 | * POST /onboarding/create-campaign. |
| 138 | 113 | * |
| 139 | - * Creates a published campaign post + writes its meta. Returns the new | |
| 114 | + * Creates a draft campaign post + writes its meta. Returns the new | |
| 140 | 115 | * campaign id + edit URL so the JS can persist it in onboarding state. |
| 141 | 116 | * |
| 142 | 117 | * @param WP_REST_Request $request Request. |
| 143 | 118 | * @return WP_REST_Response|WP_Error |
| @@ -169,16 +144,12 @@ | ||
| 169 | 144 | if ( ! in_array( $goal_type, self::GOAL_TYPES, true ) ) { |
| 170 | 145 | $goal_type = 'raised_amount'; |
| 171 | 146 | } |
| 172 | 147 | |
| 173 | - // Publish the campaign so it behaves like one created via the normal | |
| 174 | - // flow: the save_post_suredonation_cmpgn hook auto-creates its default | |
| 175 | - // donation form, and the campaign becomes selectable in the Donation | |
| 176 | - // Form block (whose query is limited to published campaigns). | |
| 177 | 148 | $result = wp_insert_post( |
| 178 | 149 | [ |
| 179 | 150 | 'post_type' => Campaign_Cpt::POST_TYPE, |
| 180 | - 'post_status' => 'publish', | |
| 151 | + 'post_status' => 'draft', | |
| 181 | 152 | 'post_title' => $name, |
| 182 | 153 | 'post_excerpt' => $description, |
| 183 | 154 | 'post_author' => get_current_user_id(), |
| 184 | 155 | ], |
| @@ -230,10 +201,8 @@ | ||
| 230 | 201 | * @return WP_REST_Response |
| 231 | 202 | * @since 1.0.0 |
| 232 | 203 | */ |
| 233 | 204 | public function save_user_details( $request ) { |
| 234 | - $onboarding = Onboarding::get_instance(); | |
| 235 | - | |
| 236 | 205 | $payload = [ |
| 237 | 206 | 'first_name' => sanitize_text_field( (string) $request->get_param( 'first_name' ) ), |
| 238 | 207 | 'last_name' => sanitize_text_field( (string) $request->get_param( 'last_name' ) ), |
| 239 | 208 | 'email' => sanitize_email( (string) $request->get_param( 'email' ) ), |
| @@ -239,22 +208,26 @@ | ||
| 239 | 208 | 'email' => sanitize_email( (string) $request->get_param( 'email' ) ), |
| 240 | 209 | 'opted_in' => (bool) $request->get_param( 'opted_in' ), |
| 241 | 210 | ]; |
| 242 | 211 | |
| 243 | - $onboarding->set_user_details( $payload ); | |
| 212 | + Onboarding::get_instance()->set_user_details( $payload ); | |
| 244 | 213 | |
| 214 | + // Persist the usage-tracking opt-in as its own option so other | |
| 215 | + // plugin code (analytics, telemetry pings) can check it without | |
| 216 | + // loading the consolidated onboarding details. Site option — the | |
| 217 | + // BSF Analytics library reads it via get_site_option(), so the | |
| 218 | + // write must use the same scope to stay in sync on multisite. | |
| 245 | 219 | update_site_option( |
| 246 | 220 | 'suredonation_usage_optin', |
| 247 | 221 | $payload['opted_in'] ? 'yes' : 'no' |
| 248 | 222 | ); |
| 249 | 223 | |
| 250 | - if ( ! $onboarding->is_lead_sent() && $this->forward_lead_to_crm( $payload ) ) { | |
| 251 | - $onboarding->mark_lead_sent(); | |
| 252 | - } | |
| 253 | - | |
| 254 | 224 | /** |
| 255 | 225 | * Fires after onboarding lead-capture details are persisted. |
| 256 | 226 | * |
| 227 | + * Listeners (e.g. Pro analytics) can forward the payload to a | |
| 228 | + * metrics endpoint when `opted_in` is true. | |
| 229 | + * | |
| 257 | 230 | * @since 1.0.0 |
| 258 | 231 | * |
| 259 | 232 | * @param array<string,mixed> $payload Sanitised payload. |
| 260 | 233 | */ |
| @@ -260,85 +233,6 @@ | ||
| 260 | 233 | */ |
| 261 | 234 | do_action( 'suredonation_onboarding_user_details_saved', $payload ); |
| 262 | 235 | |
| 263 | 236 | return new WP_REST_Response( [ 'success' => true ] ); |
| 264 | - } | |
| 265 | - | |
| 266 | - /** | |
| 267 | - * Generate lead. | |
| 268 | - * | |
| 269 | - * @param array<string,mixed> $payload Sanitised lead-capture payload. | |
| 270 | - * @return bool True when the CRM accepted the lead, false otherwise. | |
| 271 | - * @since 1.1.2 | |
| 272 | - */ | |
| 273 | - private function forward_lead_to_crm( array $payload ) { | |
| 274 | - $email_raw = $payload['email'] ?? ''; | |
| 275 | - $email = is_string( $email_raw ) ? sanitize_email( $email_raw ) : ''; | |
| 276 | - if ( empty( $email ) || ! is_email( $email ) ) { | |
| 277 | - return false; | |
| 278 | - } | |
| 279 | - | |
| 280 | - $url = 'https://metrics.brainstormforce.com/wp-json/bsf-metrics-server/v1/subscribe'; | |
| 281 | - | |
| 282 | - if ( defined( 'SUREDONATION_METRICS_ENDPOINT' ) && is_string( SUREDONATION_METRICS_ENDPOINT ) ) { | |
| 283 | - $url = SUREDONATION_METRICS_ENDPOINT; | |
| 284 | - } | |
| 285 | - | |
| 286 | - /** | |
| 287 | - * Filters the endpoint. | |
| 288 | - * | |
| 289 | - * @since 1.1.2 | |
| 290 | - * | |
| 291 | - * @param string $url Endpoint URL. | |
| 292 | - * @param array<string,mixed> $payload Lead payload being sent. | |
| 293 | - */ | |
| 294 | - $filtered = apply_filters( 'suredonation_metrics_subscribe_url', $url, $payload ); | |
| 295 | - $url = is_string( $filtered ) ? $filtered : $url; | |
| 296 | - | |
| 297 | - if ( '' === $url ) { | |
| 298 | - return false; | |
| 299 | - } | |
| 300 | - | |
| 301 | - $first_name = isset( $payload['first_name'] ) && is_string( $payload['first_name'] ) ? $payload['first_name'] : ''; | |
| 302 | - $last_name = isset( $payload['last_name'] ) && is_string( $payload['last_name'] ) ? $payload['last_name'] : ''; | |
| 303 | - $domain = wp_parse_url( home_url(), PHP_URL_HOST ); | |
| 304 | - $domain = is_string( $domain ) ? $domain : ''; | |
| 305 | - | |
| 306 | - $body = wp_json_encode( | |
| 307 | - [ | |
| 308 | - // Lowercase keys satisfy the current BSF Metrics REST args. | |
| 309 | - 'email' => $email, | |
| 310 | - 'first_name' => $first_name, | |
| 311 | - 'last_name' => $last_name, | |
| 312 | - 'domain' => $domain, | |
| 313 | - 'source' => 'suredonation', | |
| 314 | - // Legacy uppercase keys kept for backward compatibility. | |
| 315 | - 'EMAIL' => $email, | |
| 316 | - 'FIRSTNAME' => $first_name, | |
| 317 | - 'LASTNAME' => $last_name, | |
| 318 | - 'DOMAIN' => $domain, | |
| 319 | - ] | |
| 320 | - ); | |
| 321 | - | |
| 322 | - if ( false === $body ) { | |
| 323 | - return false; | |
| 324 | - } | |
| 325 | - | |
| 326 | - // `source` identifies the originating plugin on the shared CRM server. | |
| 327 | - // wp_safe_remote_post with WP's default 5s timeout keeps a slow or | |
| 328 | - // hung endpoint from stalling onboarding completion. | |
| 329 | - $response = wp_safe_remote_post( | |
| 330 | - $url, | |
| 331 | - [ | |
| 332 | - 'headers' => [ 'Content-Type' => 'application/json' ], | |
| 333 | - 'body' => $body, | |
| 334 | - ] | |
| 335 | - ); | |
| 336 | - | |
| 337 | - if ( is_wp_error( $response ) ) { | |
| 338 | - return false; | |
| 339 | - } | |
| 340 | - | |
| 341 | - $code = (int) wp_remote_retrieve_response_code( $response ); | |
| 342 | - return in_array( $code, [ 200, 201, 204 ], true ); | |
| 343 | 237 | } |
| 344 | 238 | } |