| 1 |
<?php |
| 2 |
/** |
| 3 |
* Shared base for captcha validations with a configurable spam action |
| 4 |
* (reCAPTCHA v2 and v3). |
| 5 |
* |
| 6 |
* The secret key, the flag key and the per-form spam action live on the |
| 7 |
* validation object (not on the Element) so they survive the encrypted-form |
| 8 |
* serialization round trip: Element::__sleep() only keeps attributes, label, |
| 9 |
* validation and errors. |
| 10 |
* |
| 11 |
* Spam action ($spamAction): |
| 12 |
* - '' / 'reject' - a failed check makes validation fail (visible error). |
| 13 |
* The empty value only occurs on validators serialized before the spam |
| 14 |
* action existed (encrypted-form round trip of an already-rendered page); |
| 15 |
* new renders always receive the explicit value resolved by |
| 16 |
* accua_forms_captcha_spam_action() ('spam' by default). |
| 17 |
* - 'spam' / 'trash' / 'delete' - silent classification: a failed check lets |
| 18 |
* the submission pass validation but flags it via self::$spam_flagged, |
| 19 |
* keyed by $captchaAction; the submission handler then marks the stored |
| 20 |
* submission as Spam, trashes it or deletes it, and skips the emails. |
| 21 |
* |
| 22 |
* @package Contact Forms |
| 23 |
*/ |
| 24 |
|
| 25 |
abstract class AccuaForm_Validation_CaptchaSpam extends Validation { |
| 26 |
protected $privateKey; |
| 27 |
|
| 28 |
/** |
| 29 |
* Request-scoped flag key, accuaform_{fid}. For reCAPTCHA v3 it doubles as |
| 30 |
* the expected Google action; for v2 it is only the registry key. |
| 31 |
* |
| 32 |
* @var string |
| 33 |
*/ |
| 34 |
protected $captchaAction = ''; |
| 35 |
protected $spamAction = ''; |
| 36 |
|
| 37 |
/** |
| 38 |
* Flag keys (accuaform_{fid}) whose captcha verification failed during this |
| 39 |
* request while a silent spam action was configured, mapped to the silent |
| 40 |
* action that validator was configured with. Shared by all captcha |
| 41 |
* validators (the static lives on this base class). |
| 42 |
* |
| 43 |
* The action is recorded rather than a bare true because v2 and v3 have |
| 44 |
* different defaults ('reject' and 'spam'): on a form carrying both, the |
| 45 |
* submission handler must follow the action of the field that actually |
| 46 |
* failed, not the first captcha field it happens to find. |
| 47 |
* |
| 48 |
* @var array<string, string> |
| 49 |
*/ |
| 50 |
protected static $spam_flagged = array(); |
| 51 |
|
| 52 |
/** |
| 53 |
* Whether the current request's submission for the given flag key was |
| 54 |
* silently classified as spam. |
| 55 |
* |
| 56 |
* @param string $captcha_action The per-form flag key (accuaform_{fid}). |
| 57 |
* @return bool |
| 58 |
*/ |
| 59 |
public static function isSpamFlagged( $captcha_action ) { |
| 60 |
return ! empty( self::$spam_flagged[ $captcha_action ] ); |
| 61 |
} |
| 62 |
|
| 63 |
/** |
| 64 |
* The silent spam action of the validator that flagged this request. |
| 65 |
* |
| 66 |
* @param string $captcha_action The per-form flag key (accuaform_{fid}). |
| 67 |
* @return string 'spam', 'trash', 'delete', or '' when nothing was flagged. |
| 68 |
*/ |
| 69 |
public static function getSpamAction( $captcha_action ) { |
| 70 |
return isset( self::$spam_flagged[ $captcha_action ] ) ? (string) self::$spam_flagged[ $captcha_action ] : ''; |
| 71 |
} |
| 72 |
|
| 73 |
/** |
| 74 |
* Handle a failed verification according to the configured spam action: |
| 75 |
* fail validation (reject) or flag the submission and let it pass (silent). |
| 76 |
* |
| 77 |
* @return bool The value isValid() must return. |
| 78 |
*/ |
| 79 |
protected function failed() { |
| 80 |
if ( in_array( $this->spamAction, array( 'spam', 'trash', 'delete' ), true ) ) { |
| 81 |
self::$spam_flagged[ $this->captchaAction ] = $this->spamAction; |
| 82 |
return true; |
| 83 |
} |
| 84 |
return false; |
| 85 |
} |
| 86 |
} |
| 87 |
|