$values Unslashed values keyed by field key. * @param array $context Request metadata stored with the entry. * @param array|null $signals Optional. Spam signals, see SpamGuard::signals(). * Default read from the request. * * @return int|WP_Error The new submission ID, or the reason it was refused. */ public function process( int $form_id, array $values, array $context = array(), ?array $signals = null ): int|WP_Error { $form = $form_id > 0 ? $this->forms->find( $form_id ) : null; if ( ! $form instanceof Form || ! $form->is_published() ) { return new WP_Error( 'formpilot_unknown_form', __( 'This form is no longer available.', 'devplugins-custom-form-builder' ), array( 'status' => 404 ) ); } $rejection = $this->guard->check( $form, $values, $signals ); if ( $rejection instanceof WP_Error ) { $code = (string) $rejection->get_error_code(); /* * An attempt refused as spam counts against the rate limit. Validation * failures deliberately do not: mistyping an email address is not abuse, * and holding it against the visitor turns a typo into a lockout. */ if ( 'formpilot_spam' === $code ) { $this->guard->record_rejection( $form->id() ); } /* * The status describes what happened. Answering 429 to everything told a * well behaved client that a cross-origin post or a blocklisted address * was a rate limit and invited it to retry something that can never * succeed. Only the two genuinely time-based refusals say 429. * * `add_data()` replaces the bag rather than adding to it, which used to * destroy the score `formpilot_spam` carries, so the existing data is merged. */ $status = match ( $code ) { 'formpilot_throttled', 'formpilot_rate_limited' => 429, 'formpilot_cross_origin', 'formpilot_blocked' => 403, default => 422, }; $data = $rejection->get_error_data( $code ); $data = is_array( $data ) ? $data : array(); $rejection->add_data( array_merge( $data, array( 'status' => $status ) ), $code ); return $rejection; } $fields = $this->fields->for_form( $form->id() ); if ( array() === $fields ) { return new WP_Error( 'formpilot_empty_form', __( 'This form has no fields.', 'devplugins-custom-form-builder' ), array( 'status' => 400 ) ); } $result = $this->validator->validate( $fields, $values ); if ( ! $result->is_valid() ) { return new WP_Error( 'formpilot_validation_failed', __( 'Please check the highlighted fields and try again.', 'devplugins-custom-form-builder' ), array( 'status' => 422, 'errors' => $result->errors(), ) ); } $entries = array(); $accepted = $result->values(); foreach ( $fields as $field ) { $key = $field->field_key(); if ( ! array_key_exists( $key, $accepted ) ) { continue; } $entries[] = array( 'field_id' => $field->id(), 'key' => $key, 'value' => $accepted[ $key ], ); } $submission_id = $this->submissions->create( $form->id(), $entries, $context ); if ( 0 === $submission_id ) { // The files were written during validation; nothing points at them now. $this->validator->discard_stored_uploads( $fields, $accepted ); return new WP_Error( 'formpilot_storage_failed', __( 'Your submission could not be saved. Please try again.', 'devplugins-custom-form-builder' ), array( 'status' => 500 ) ); } $this->forms->increment_submission_count( $form->id() ); $this->guard->record( $form->id() ); return $submission_id; } /** * Returns the form a submission targets. * * @param int $form_id Form ID. * * @return Form|null The form, or null when it cannot receive submissions. */ public function form( int $form_id ): ?Form { $form = $form_id > 0 ? $this->forms->find( $form_id ) : null; return $form instanceof Form && $form->is_published() ? $form : null; } /** * Collects the request metadata stored with a submission. * * @param int $post_id Optional. Post holding the form. Default 0. * * @return array The context. */ public function context( int $post_id = 0 ): array { $user_agent = Request::server( 'HTTP_USER_AGENT' ); $address = Request::ip(); return array( 'user_id' => get_current_user_id(), /** This filter is documented in src/Frontend/SubmissionHandler.php */ 'ip_address' => (string) apply_filters( 'formpilot_submission_ip', wp_privacy_anonymize_ip( $address ), $address ), 'user_agent' => $user_agent, 'referer' => (string) wp_get_referer(), 'post_id' => $post_id, ); } }