integration = $integration; } /** * Handle WPForms submission. * * @since 1.0.0 * @since 1.4.0 Attach payment signals to submission context. * @since 1.4.0 Skip administrative re-processing (WPForms "Mark as Not Spam"). * @since 1.8.0 Replay held notifications when the queue rejects a submission or processing throws. * * @param array $fields Form fields data. * @param array $entry Original entry data for notification recovery. * @param array $form_data Form configuration. * @param int|mixed $entry_id WPForms entry ID (can be int or string depending on WPForms version). */ public function handle_submission( array $fields, array $entry, array $form_data, $entry_id ): void { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh, Generic.Metrics.CyclomaticComplexity.MaxExceeded // WPForms Pro re-fires `wpforms_process_complete` when an admin marks a spam // entry as not spam (SpamEntry::process_complete()). A genuine submission // always fires `wpforms_process` earlier in the same request, so its absence // means this is an administrative re-fire - never a live form submission. if ( ! did_action( 'wpforms_process' ) ) { return; } // Ensure entry_id is an integer. $entry_id = (int) $entry_id; // Skip if integration is disabled. if ( ! $this->integration->is_enabled() ) { return; } // Check if anti-spam is enabled for this form. if ( ! $this->integration->is_form_protected( $form_data ) ) { return; } $form_settings = $this->integration->get_admin_settings()->get_form_settings( $form_data ); $tracking_mode = $this->integration->is_tracking_mode_for_form( $form_data ); $strategy = $this->integration->get_processing_strategy( $form_data ); if ( $strategy === WPFormsIntegration::STRATEGY_SYNC_SAVE ) { $this->handle_sync_with_entries( $fields, $form_data, $entry_id ); return; } if ( $strategy === WPFormsIntegration::STRATEGY_SYNC_BLOCK ) { // The verdict was already handled by maybe_handle_sync_submission(), which ran // before the entry existed - attach the entry now so status sync can find it. $this->integration->link_sync_submission_to_entry( (int) ( $form_data['id'] ?? 0 ), $entry_id ); return; } // Check if API key is configured. if ( ! SettingsHelper::has_api_key() ) { // No API key configured - skip tracking and API calls. return; } try { // Get form metadata. $meta = $this->integration->get_form_meta( $form_data ); $meta['entry_id'] = $entry_id; $meta['tracking_mode'] = $tracking_mode; $payment = $this->integration->get_payment_signals( $form_data, $fields ); if ( ! empty( $payment ) ) { $meta['payment'] = $payment; } // Process submission. $submission_id = $this->integration->process_submission( $fields, $meta ); if ( ! empty( $meta['queue_failed'] ) ) { $this->integration->register_queue_failure( (int) $form_data['id'], $entry_id ); $this->integration->release_held_notifications( $fields, $entry, $form_data, $entry_id ); if ( $entry_id ) { wpforms()->obj( 'entry_meta' )->add( [ 'entry_id' => $entry_id, 'form_id' => $form_data['id'], 'type' => 'spam_status', 'data' => 'failed', ] ); } return; } // Store submission ID in entry meta for later reference. if ( $submission_id && $entry_id ) { wpforms()->obj( 'entry_meta' )->add( [ 'entry_id' => $entry_id, 'form_id' => $form_data['id'], 'type' => 'activelayer_submission_id', 'data' => $submission_id, ] ); } } catch ( \Throwable $e ) { // Preserve fail-open delivery even when storage or enqueueing throws. $this->integration->register_queue_failure( (int) ( $form_data['id'] ?? 0 ), $entry_id ); $this->integration->release_held_notifications( $fields, $entry, $form_data, $entry_id ); Logger::log( 'Failed to queue submission', [ 'error' => $e->getMessage(), 'form_id' => $form_data['id'] ?? 0, 'entry_id' => $entry_id, ] ); } } /** * Handle synchronous processing when entries are available. * * Runs a blocking API check after the entry is created, then marks * the entry as spam or allows emails based on the verdict. * * Reached for forms that store spam entries - marking the entry afterwards is what * that setting asks for - and for payment forms. Forms that do not store spam * entries are verified before entry creation in * WPFormsIntegration::maybe_handle_sync_submission() instead. * * @since 1.1.0 * @since 1.4.0 Attach payment signals to submission context. * @since 1.7.0 No longer reached by forms that do not store spam entries. * * @param array $fields Form fields data. * @param array $form_data Form configuration. * @param int $entry_id WPForms entry ID. */ private function handle_sync_with_entries( array $fields, array $form_data, int $entry_id ): void { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh, Generic.Metrics.CyclomaticComplexity.MaxExceeded $tracking_mode = $this->integration->is_tracking_mode_for_form( $form_data ); $meta = $this->integration->get_form_meta( $form_data ); $meta['entry_id'] = $entry_id; $meta['tracking_mode'] = $tracking_mode; $payment = $this->integration->get_payment_signals( $form_data, $fields ); if ( ! empty( $payment ) ) { $meta['payment'] = $payment; } // Tracking mode: queue async, emails already flowing normally. if ( $tracking_mode ) { try { $submission_id = $this->integration->process_submission( $fields, $meta ); if ( $submission_id && $entry_id ) { wpforms()->obj( 'entry_meta' )->add( [ 'entry_id' => $entry_id, 'form_id' => $form_data['id'], 'type' => 'activelayer_submission_id', 'data' => $submission_id, ] ); } } catch ( \Exception $e ) { Logger::log( 'WPForms sync+entries tracking error', [ 'form_id' => $meta['form_id'] ?? 0, 'error' => $e->getMessage(), ] ); } return; } // Non-tracking: synchronous API check. $result = $this->integration->process_submission_synchronously( $fields, $meta ); $submission_id = $result['submission_id'] ?? ''; $verdict = $result['verdict'] ?? ''; // Link submission to entry. if ( $submission_id && $entry_id ) { wpforms()->obj( 'entry_meta' )->add( [ 'entry_id' => $entry_id, 'form_id' => $form_data['id'], 'type' => 'activelayer_submission_id', 'data' => $submission_id, ] ); } // Handle API failure: safe-by-default, re-send emails. if ( empty( $result['success'] ) ) { if ( $submission_id ) { $this->integration->resend_clean_emails( $submission_id ); } else { $this->integration->allow_sync_emails(); } if ( $entry_id ) { wpforms()->obj( 'entry_meta' )->add( [ 'entry_id' => $entry_id, 'form_id' => $form_data['id'], 'type' => 'spam_status', 'data' => 'failed', ] ); } return; } // Write spam_status meta. if ( $entry_id ) { wpforms()->obj( 'entry_meta' )->add( [ 'entry_id' => $entry_id, 'form_id' => $form_data['id'], 'type' => 'spam_status', 'data' => $verdict, ] ); } if ( $verdict === 'spam' ) { // Mark entry as spam. Emails remain blocked. $this->integration->mark_entry_spam( $entry_id, $form_data ); } else { // Clean: re-send emails via EmailReconstructor. $this->integration->resend_clean_emails( $submission_id ); } } }