|null */ private static ?array $providers = null; /** * Available providers. Add more with the tflexforms_captcha_providers filter. * * @return array */ public static function providers(): array { if ( null === self::$providers ) { $list = array( new Captcha_Turnstile(), new Captcha_Recaptcha(), new Captcha_Hcaptcha() ); /** * Filters CAPTCHA providers. * * @param Captcha_Provider[] $list Provider instances. */ $list = (array) apply_filters( 'tflexforms_captcha_providers', $list ); self::$providers = array(); foreach ( $list as $provider ) { if ( $provider instanceof Captcha_Provider && preg_match( '/^[a-z0-9_]+$/', $provider->id() ) ) { self::$providers[ $provider->id() ] = $provider; } } } return self::$providers; } /** * The selected provider when it has both keys, otherwise null. */ public static function active(): ?Captcha_Provider { $id = (string) Settings::get( 'captcha_provider' ); $providers = self::providers(); if ( '' === $id || ! isset( $providers[ $id ] ) ) { return null; } if ( '' === Settings::key( $id, 'site_key' ) || '' === Settings::key( $id, 'secret_key' ) ) { return null; } return $providers[ $id ]; } /** * Whether a form asks for CAPTCHA. * * @param Form $form Form. */ public static function requested_by( Form $form ): bool { return ! empty( $form->settings['captcha'] ); } /** * Widget markup for a form ('' when CAPTCHA is off). Enqueues the provider script. * * @param Form $form Form. */ public static function widget( Form $form ): string { $provider = self::requested_by( $form ) ? self::active() : null; if ( null === $provider ) { return ''; } $site_key = Settings::key( $provider->id(), 'site_key' ); wp_enqueue_script( 'tflexforms-captcha-' . $provider->id(), $provider->script_url( $site_key ), array(), null, // phpcs:ignore WordPress.WP.EnqueuedResourceParameters.MissingVersion -- Third-party script; a ver query arg can break provider caching. array( 'in_footer' => true, 'strategy' => 'async', ) ); return '
' . $provider->widget_html( $site_key ) . '
'; } /** * Verifies the CAPTCHA for a submission. * * @param Form $form Form. * @param array $input Raw input. * @param string $ip Visitor IP. * @return bool True when allowed (passed, or CAPTCHA not in use). */ public static function passes( Form $form, array $input, string $ip ): bool { if ( ! self::requested_by( $form ) ) { return true; } $provider = self::active(); if ( null === $provider ) { // Requested but not configured: the configuration check warns the admin. return true; } $token = $input[ $provider->token_field() ] ?? ''; $verdict = $provider->verify( is_string( $token ) ? trim( $token ) : '', Settings::key( $provider->id(), 'secret_key' ), $ip, Settings::key( $provider->id(), 'site_key' ) ); if ( '' !== $verdict->warning ) { update_option( self::WARNING_OPTION, array( 'provider' => $provider->id(), 'message' => $verdict->warning, 'time' => time(), ), false ); } /** * Fires after a CAPTCHA check, e.g. for logging failures. * * @param Captcha_Verdict $verdict Outcome. * @param Captcha_Provider $provider Provider. * @param Form $form Form. */ do_action( 'tflexforms_captcha_verified', $verdict, $provider, $form ); return $verdict->passed; } }