field_renderer = $field_renderer; $this->context_factory = $context_factory; $this->result_mapper = $result_mapper; $this->spam_engine = $spam_engine; $this->settings_repository = $settings_repository; $this->event_logger = $event_logger; $this->availability_callback = $availability_callback; $this->process_callback = $process_callback; } /** * Get stable integration source key. */ public function get_key(): string { return self::KEY; } /** * Get integration label. */ public function get_label(): string { if ( function_exists( 'esc_html__' ) ) { return esc_html__( 'WPForms Lite', '2o2code-spamless-forms' ); } return 'WPForms Lite'; } /** * Get stable integration slug. */ public function get_slug(): string { return $this->get_key(); } /** * Whether WPForms appears available. */ public function is_available(): bool { if ( is_callable( $this->availability_callback ) ) { return (bool) call_user_func( $this->availability_callback ); } return defined( 'WPFORMS_VERSION' ) || class_exists( 'WPForms' ) || function_exists( 'wpforms' ); } /** * Register WPForms hooks. */ public function register_hooks(): void { if ( ! $this->is_available() || ! $this->is_enabled() || ! function_exists( 'add_action' ) ) { return; } add_action( 'wpforms_display_submit_before', array( $this, 'render_protection_fields' ), 20, 1 ); add_action( 'wpforms_process', array( $this, 'validate_submission' ), 5, 3 ); } /** * Render protection fields inside WPForms forms. * * @param array $form_data WPForms form data. */ public function render_protection_fields( array $form_data = array() ): void { // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- ProtectionFieldRenderer returns escaped hidden fields. echo $this->field_renderer->render_fields( $this->context_factory->get_form_id( $form_data ) ); } /** * Validate a WPForms submission through the shared engine. * * @param array $fields WPForms fields. * @param array $entry WPForms entry data. * @param array $form_data WPForms form data. */ public function validate_submission( array $fields, array $entry, array $form_data ): void { if ( ! $this->is_enabled() ) { return; } $settings = SettingsSnapshot::from_array( $this->settings_repository->get_settings() ); $context = $this->context_factory->build_from_submission( $fields, $entry, $form_data, $this->get_post_data(), $this->get_rate_limit_identifier() ); $decision = $this->spam_engine->evaluate( $context, $settings ); if ( $this->event_logger instanceof EventLogger ) { $this->event_logger->log_decision( self::KEY, $context, $decision ); } $this->result_mapper->map_process_result( $decision, $form_data, $this->get_process_object() ); } /** * Whether the integration is enabled in settings. */ private function is_enabled(): bool { $settings = $this->settings_repository->get_settings(); return ! empty( $settings[ self::SETTINGS_ENABLED_KEY ] ); } /** * Get unslashed posted data. * * @return array */ private function get_post_data(): array { // phpcs:disable WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Public WPForms submissions are normalized and sanitized by the context factory before evaluation. $posted_data = wp_unslash( $_POST ); // phpcs:enable WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized return $posted_data; } /** * Get request identifier for immediate hashing by the rate-limit check. */ private function get_rate_limit_identifier(): string { // phpcs:disable WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- The value is sanitized here and hashed before storage by the rate-limit check. if ( ! isset( $_SERVER['REMOTE_ADDR'] ) ) { return ''; } $remote_addr = (string) wp_unslash( $_SERVER['REMOTE_ADDR'] ); // phpcs:enable WordPress.Security.ValidatedSanitizedInput.InputNotSanitized return sanitize_text_field( $remote_addr ); } /** * Get WPForms process object if available. * * @return object|null */ private function get_process_object() { if ( is_callable( $this->process_callback ) ) { $process = call_user_func( $this->process_callback ); return is_object( $process ) ? $process : null; } if ( ! function_exists( 'wpforms' ) ) { return null; } $wpforms = wpforms(); if ( ! is_object( $wpforms ) ) { return null; } if ( method_exists( $wpforms, 'obj' ) ) { $process = $wpforms->obj( 'process' ); if ( is_object( $process ) ) { return $process; } } if ( property_exists( $wpforms, 'process' ) && is_object( $wpforms->process ) ) { return $wpforms->process; } return null; } }