Skip to content

Instantly share code, notes, and snippets.

@kagg-design
Created October 10, 2022 08:46
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save kagg-design/0e6d59da1557a662717f22f27cab3ed1 to your computer and use it in GitHub Desktop.
Save kagg-design/0e6d59da1557a662717f22f27cab3ed1 to your computer and use it in GitHub Desktop.
Turnstile integration with Quform
<?php
/**
* Turnstile class file.
*
* @package wtei-core
*/
namespace WTEICore;
use JsonException;
use Quform_Element_Page;
use Quform_Form;
/**
* Class Turnstile
*/
class Turnstile {
/**
* Site key.
*
* @var string
*/
private string $site_key;
/**
* Secret key.
*
* @var string
*/
private string $secret_key;
/**
* Form shown.
*
* @var bool
*/
private bool $form_shown = false;
/**
* Init class.
*/
public function init(): void {
add_action( 'plugins_loaded', [ $this, 'hooks' ] );
}
/**
* Add hooks.
*
* @return void
*/
public function hooks(): void {
if ( ! class_exists( Quform::class, false ) ) {
return;
}
$this->site_key = defined( 'TURNSTILE_SITE_KEY' ) ? TURNSTILE_SITE_KEY : '';
$this->secret_key = defined( 'TURNSTILE_SECRET_KEY' ) ? TURNSTILE_SECRET_KEY : '';
add_action( 'do_shortcode_tag', [ $this, 'add_turnstile' ], 10, 4 );
add_action( 'wp_print_footer_scripts', [ $this, 'print_footer_scripts' ], 0 );
add_filter( 'quform_pre_validate', [ $this, 'validate' ], 10, 2 );
}
/**
* Filters the output created by a shortcode callback and adds turnstile.
*
* @param string $output Shortcode output.
* @param string $tag Shortcode name.
* @param array|string $attr Shortcode attributes array or empty string.
* @param array $m Regular expression match array.
*
* @return string
* @noinspection PhpMissingParamTypeInspection
*/
public function add_turnstile( string $output, string $tag, $attr, $m ): string {
if ( 'quform' !== $tag ) {
return $output;
}
$this->form_shown = true;
$max_id = '9999_9999';
if ( preg_match_all( '/quform-element-(\d+?)_(\d+)\D/', $output, $m ) ) {
$element_ids = array_map( 'intval', array_unique( $m[2] ) );
$max_id = $m[1][0] . '_' . ( max( $element_ids ) + 1 );
}
ob_start();
?>
<div class="quform-element quform-element-turnstile quform-element-<?php echo esc_attr( $max_id ); ?> quform-cf quform-element-required quform-turnstile-no-size">
<div class="quform-spacer">
<div class="quform-inner quform-inner-turnstile quform-inner-<?php echo esc_attr( $max_id ); ?>">
<div class="quform-input quform-input-turnstile quform-input-<?php echo esc_attr( $max_id ); ?> quform-cf">
<div
class="cf-turnstile quform-turnstile"
data-sitekey="<?php echo esc_attr( $this->site_key ); ?>">
</div>
<noscript><?php esc_html_e( 'Please enable JavaScript to submit this form.', 'wtei' ); ?></noscript>
</div>
</div>
</div>
</div>
<?php
$turnstile = ob_get_clean();
return preg_replace( '/(<div class="quform-element quform-element-submit)/', $turnstile . '$1', $output );
}
/**
* Print footer scripts.
*
* @return void
*/
public function print_footer_scripts(): void {
if ( ! $this->form_shown ) {
return;
}
// phpcs:disable WordPress.WP.EnqueuedResources.NonEnqueuedScript
?>
<script src="https://challenges.cloudflare.com/turnstile/v0/api.js" async defer></script>
<?php
// phpcs:enable WordPress.WP.EnqueuedResources.NonEnqueuedScript
}
/**
* Validate Turnstile.
*
* @param array $result Result.
* @param Quform_Form $form Form.
*
* @return array
* @throws JsonException JsonException.
*/
public function validate( array $result, Quform_Form $form ): array {
$page = $form->getCurrentPage();
$page_id = $page ? $page->getId() : 0;
$turnstile_name = $this->get_max_element_id( $page );
$turnstile_error = [
'type' => 'error',
'error' =>
[
'enabled' => false,
'title' => '',
'content' => '',
],
'errors' => [ $turnstile_name => '' ],
'page' => $page_id,
];
// Nonce is verified by QuForm.
// phpcs:disable WordPress.Security.NonceVerification.Missing
$response = isset( $_POST['cf-turnstile-response'] ) ?
sanitize_text_field( wp_unslash( $_POST['cf-turnstile-response'] ) ) :
'';
// phpcs:enable WordPress.Security.NonceVerification.Missing
$validation = wp_remote_post(
'https://challenges.cloudflare.com/turnstile/v0/siteverify',
[
'body' => [
'secret' => $this->secret_key,
'response' => $response,
],
]
);
if ( is_wp_error( $validation ) ) {
$turnstile_error['errors'] = [ $turnstile_name => $validation->get_error_message() ];
return $turnstile_error;
}
$body = json_decode( $validation['body'], true, 512, JSON_THROW_ON_ERROR );
if ( ! $body['success'] ) {
$turnstile_error['errors'] = [ $turnstile_name => $this->get_turnstile_error_message( $body['error-codes'] ) ];
return $turnstile_error;
}
return $result;
}
/**
* Get max form element id.
*
* @param Quform_Element_Page|null $page Current page.
*
* @return string
*/
private function get_max_element_id( ?Quform_Element_Page $page ): string {
$max_id = '9999_9999';
if ( null === $page ) {
return $max_id;
}
$ids = array_map(
static function ( $element ) {
return $element->getId();
},
$page->getElements()
);
return $page->getForm()->getId() . '_' . ( max( $ids ) + 1 );
}
/**
* Get turnstile error message.
*
* @param array $error_codes Error codes.
*
* @return string
*/
private function get_turnstile_error_message( array $error_codes ): string {
$errors = [
'missing-input-secret' => __( 'The secret parameter was not passed.', 'wtei' ),
'invalid-input-secret' => __( 'The secret parameter was invalid or did not exist.', 'wtei' ),
'missing-input-response' => __( 'The response parameter was not passed.', 'wtei' ),
'invalid-input-response' => __( 'The response parameter is invalid or has expired.', 'wtei' ),
'bad-request' => __( 'The request was rejected because it was malformed.', 'wtei' ),
'timeout-or-duplicate' => __( 'The response parameter has already been validated before.', 'wtei' ),
'internal-error' => __( 'An internal error happened while validating the response. The request can be retried.', 'wtei' ),
];
$message_arr = [];
foreach ( $error_codes as $error_code ) {
if ( array_key_exists( $error_code, $errors ) ) {
$message_arr[] = $errors[ $error_code ];
}
}
$header = _n( 'Turnstile error:', 'Turnstile errors:', count( $message_arr ), 'wtei' );
return $header . ' ' . implode( '; ', $message_arr );
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment