[ 'challenge_script' => 'https://challenges.cloudflare.com/turnstile/v0/api.js', 'verify_endpoint' => 'https://challenges.cloudflare.com/turnstile/v0/siteverify', 'sitekey_name' => 'cf-sitekey', 'secretkey_name' => 'cf-secretkey', 'class_name' => 'cf-turnstile', 'response_name' => 'cf-turnstile-response', 'function_name' => 'getTurnstile', ], 'google' => [ 'challenge_script' => 'https://www.google.com/recaptcha/api.js', 'verify_endpoint' => 'https://www.google.com/recaptcha/api/siteverify', 'sitekey_name' => 'g-sitekey', 'secretkey_name' => 'g-secretkey', 'class_name' => 'g-recaptcha', 'response_name' => 'g-recaptcha-response', 'function_name' => 'getCaptcha', ], ]; private const DEFAULT = 'cloudflare'; public function register(EventHandler $controller) { $controller->register_hook('TPL_METAHEADER_OUTPUT', 'BEFORE', $this, 'injectScript'); $controller->register_hook('FORM_REGISTER_OUTPUT', 'BEFORE', $this, 'handleFormOutput'); $controller->register_hook('FORM_LOGIN_OUTPUT', 'BEFORE', $this, 'handleFormOutput'); $controller->register_hook('ACTION_ACT_PREPROCESS', 'BEFORE', $this, 'handleRegister'); $controller->register_hook('AUTH_LOGIN_CHECK', 'BEFORE', $this, 'handleLogin'); } private function getMode(): string { return $this->getConf('mode') ?: self::DEFAULT; } private function getHTML() { $mode = $this->getMode(); $sitekey = $this->getConf(self::CAPTCHA_PROVIDERS[$mode]['sitekey_name']); $class = self::CAPTCHA_PROVIDERS[$mode]['class_name']; return '
'; } public function injectScript(Event $event, $param) { global $ACT; if ($ACT === 'register' || $ACT === 'login') { $src = self::CAPTCHA_PROVIDERS[$this->getMode()]['challenge_script']; $event->data['script'][] = [ 'type' => 'text/javascript', 'src' => $src, 'async' => 'async', 'defer' => 'defer', '_data' => '' ]; } } public function handleFormOutput(Event $event, $param) { if(!$html = $this->getHTML()) return; $form = $event->data; $pos = $form->findPositionByAttribute('type', 'submit'); $form->addHTML($html, $pos); } private function generateResponse(Event $event): ?array { $mode = $this->getMode(); $secretkey = $this->getConf(self::CAPTCHA_PROVIDERS[$mode]['secretkey_name']); global $INPUT; $response = $INPUT->post->str(self::CAPTCHA_PROVIDERS[$mode]['response_name']); if (empty($response)) return null; $data = [ 'secret' => $secretkey, 'response' => $response, ]; $ip = $INPUT->server->str('REMOTE_ADDR'); if (!empty($ip)) $data['remoteip'] = $ip; $function = self::CAPTCHA_PROVIDERS[$mode]['function_name']; $helper = plugin_load('helper', 'validator'); $curl = $helper->$function(self::CAPTCHA_PROVIDERS[$mode]['verify_endpoint'], $data); $responseBody = curl_exec($curl); if (curl_errno($curl) || curl_getinfo($curl, CURLINFO_HTTP_CODE) !== 200) { curl_close($curl); return null; } curl_close($curl); $outcome = json_decode($responseBody, true); if (json_last_error() !== JSON_ERROR_NONE) return null; return $outcome; } private function checkToken(Event $event) { $mode = $this->getMode(); $sitekey = $this->getConf(self::CAPTCHA_PROVIDERS[$mode]['sitekey_name']); $secretkey = $this->getConf(self::CAPTCHA_PROVIDERS[$mode]['secretkey_name']); if (empty($sitekey) || empty($secretkey)) return; $response = $this->generateResponse($event); if (!is_null($response) && isset($response['success']) && $response['success'] === true) { return; } // add msg here global $INPUT; global $ACT; switch ($ACT) { case 'register': $INPUT->post->set('save', false); break; case 'login': $event->result = false; $event->preventDefault(); $event->stopPropagation(); break; } } public function handleRegister(Event $event, $param) { global $INPUT; $act = act_clean($event->data); if ($act !== 'register' || !$INPUT->bool('save')) return; $this->checkToken($event); } public function handleLogin(Event $event, $param) { global $INPUT; if (!$INPUT->bool('u')) return; $this->checkToken($event); } }