| 1 |
<?php |
| 2 |
/** |
| 3 |
* Cap (capjs.js.org) Validation |
| 4 |
* |
| 5 |
* Verifies the proof-of-work token against the self-hosted Cap Standalone |
| 6 |
* instance via its reCAPTCHA-compatible siteverify endpoint: |
| 7 |
* POST {instance}/{siteKey}/siteverify with a JSON body {secret, response}. |
| 8 |
* |
| 9 |
* The instance URL, site key and secret live on this object (not on the |
| 10 |
* Element) so they survive the encrypted-form serialization round trip: |
| 11 |
* Element::__sleep() only keeps attributes, label, validation and errors. |
| 12 |
* |
| 13 |
* @package Contact Forms |
| 14 |
*/ |
| 15 |
|
| 16 |
// phpcs:disable WordPress.Security.NonceVerification, WordPress.Security.ValidatedSanitizedInput -- Server-side CAPTCHA validation requires POST data |
| 17 |
|
| 18 |
class AccuaForm_Validation_Cap extends Validation { |
| 19 |
protected $message = 'Captcha verification failed. Please retry.'; |
| 20 |
protected $instanceUrl; |
| 21 |
protected $siteKey; |
| 22 |
protected $secretKey; |
| 23 |
|
| 24 |
public function isValid( $value ) { |
| 25 |
if ( isset( $_POST['accua-forms-cap-response'] ) && '' !== $_POST['accua-forms-cap-response'] ) { |
| 26 |
$response = stripslashes_deep( $_POST['accua-forms-cap-response'] ); |
| 27 |
} elseif ( isset( $_POST['cap-token'] ) ) { |
| 28 |
// Fallback: the hidden input injected by the cap-widget itself |
| 29 |
// (covers the edge case where our sync script did not run). |
| 30 |
$response = stripslashes_deep( $_POST['cap-token'] ); |
| 31 |
} else { |
| 32 |
$response = (string) $value; |
| 33 |
} |
| 34 |
|
| 35 |
if ( '' === $response || ! is_string( $response ) ) { |
| 36 |
return false; |
| 37 |
} |
| 38 |
|
| 39 |
if ( empty( $this->instanceUrl ) || empty( $this->siteKey ) || empty( $this->secretKey ) ) { |
| 40 |
return false; |
| 41 |
} |
| 42 |
|
| 43 |
$verify_url = trailingslashit( $this->instanceUrl ) . rawurlencode( $this->siteKey ) . '/siteverify'; |
| 44 |
|
| 45 |
$response_obj = wp_remote_post( |
| 46 |
$verify_url, |
| 47 |
array( |
| 48 |
'headers' => array( 'Content-Type' => 'application/json' ), |
| 49 |
'body' => wp_json_encode( |
| 50 |
array( |
| 51 |
'secret' => $this->secretKey, |
| 52 |
'response' => $response, |
| 53 |
) |
| 54 |
), |
| 55 |
'timeout' => 20, |
| 56 |
) |
| 57 |
); |
| 58 |
|
| 59 |
if ( is_wp_error( $response_obj ) ) { |
| 60 |
return false; |
| 61 |
} |
| 62 |
|
| 63 |
$body = wp_remote_retrieve_body( $response_obj ); |
| 64 |
$result = json_decode( $body, true ); |
| 65 |
|
| 66 |
return ! empty( $result['success'] ); |
| 67 |
} |
| 68 |
} |
| 69 |
|