Support methods to load and check CAPTCHA wherever a developer wants

This commit is contained in:
Kijin Sung 2025-12-17 17:37:03 +09:00
parent 01984210b6
commit 307661b57b
10 changed files with 127 additions and 11 deletions

View file

@ -20,9 +20,12 @@ class reCAPTCHA
self::$config = $config;
}
public static function check()
public static function check($response = null)
{
if (!$response)
{
$response = Context::get('g-recaptcha-response');
}
if (!$response)
{
throw new Exception('msg_recaptcha_invalid_response');

View file

@ -20,9 +20,12 @@ class Turnstile
self::$config = $config;
}
public static function check()
public static function check($response = null)
{
if (!$response)
{
$response = Context::get('g-recaptcha-response');
}
if (!$response)
{
throw new Exception('msg_recaptcha_invalid_response');

View file

@ -6,6 +6,7 @@
<action name="dispSpamfilterAdminDeniedWordList" type="view" menu_name="spamFilter" />
<action name="dispSpamfilterAdminConfigBlock" type="view" menu_name="spamFilter" />
<action name="dispSpamfilterAdminConfigCaptcha" type="view" menu_name="spamFilter" />
<action name="dispSpamfilterAdminConfigCaptchaTest" type="view" menu_name="spamFilter" />
<action name="procSpamfilterAdminInsertDeniedIP" type="controller" />
<action name="procSpamfilterAdminUpdateDeniedIP" type="controller" />
@ -15,6 +16,7 @@
<action name="procSpamfilterAdminDeleteDeniedWord" type="controller" />
<action name="procSpamfilterAdminInsertConfig" type="controller" />
<action name="procSpamfilterAdminInsertConfigCaptcha" type="controller" />
<action name="procSpamfilterAdminSubmitCaptchaTest" type="controller" />
</actions>
<eventHandlers>
<eventHandler before="document.manage" class="controller" method="triggerManageDocument" />

View file

@ -2,7 +2,8 @@
$lang->cmd_denied_ip = 'IP Address Blacklist';
$lang->cmd_denied_word = 'Keyword Blacklist';
$lang->cmd_config_block = 'Automatic Blocking';
$lang->cmd_captcha_config = 'CAPTCHA';
$lang->cmd_captcha_config = 'CAPTCHA Settings';
$lang->cmd_captcha_test = 'CAPTCHA Test';
$lang->add_denied_ip = 'Add IP address or range';
$lang->add_denied_word = 'Add keyword';
$lang->spamfilter = 'Spam Filter';
@ -69,7 +70,9 @@ $lang->recaptcha_target_everyone = 'Everyone';
$lang->recaptcha_target_frequency = 'Frequency';
$lang->recaptcha_target_first_time_only = 'First Time Only';
$lang->recaptcha_target_every_time = 'Every Time';
$lang->msg_recaptcha_not_configured = 'Your CAPTCHA is not properly configured.';
$lang->msg_recaptcha_connection_error = 'An error occurred while connecting to the CAPTCHA verification server.';
$lang->msg_recaptcha_server_error = 'An error occurred while verifying your CAPTCHA response.';
$lang->msg_recaptcha_invalid_response = 'Please check the CAPTCHA.';
$lang->msg_recaptcha_keys_not_set = 'Please fill in your CAPTCHA Site Key and Secret Key.';
$lang->msg_recaptcha_test_success = 'You passed the CAPTCHA test successfully.';

View file

@ -3,6 +3,7 @@ $lang->cmd_denied_ip = '스팸 IP 목록';
$lang->cmd_denied_word = '스팸 키워드 목록';
$lang->cmd_config_block = '자동 차단 설정';
$lang->cmd_captcha_config = '캡챠 설정';
$lang->cmd_captcha_test = '캡챠 테스트';
$lang->add_denied_ip = '스팸 IP 추가';
$lang->add_denied_word = '스팸 키워드 추가';
$lang->spamfilter = '스팸필터';
@ -69,7 +70,9 @@ $lang->recaptcha_target_everyone = '모든 사용자';
$lang->recaptcha_target_frequency = '캡챠 사용 빈도';
$lang->recaptcha_target_first_time_only = '최초 1회만 사용';
$lang->recaptcha_target_every_time = '매번 사용';
$lang->msg_recaptcha_not_configured = '스팸방지 CAPTCHA가 올바르게 설정되지 않았습니다.';
$lang->msg_recaptcha_connection_error = '스팸방지 CAPTCHA 서버에 접속하는 도중 오류가 발생했습니다.';
$lang->msg_recaptcha_server_error = '스팸방지 CAPTCHA 서버와 통신하는 도중 오류가 발생했습니다.';
$lang->msg_recaptcha_invalid_response = '스팸방지 기능을 체크해 주십시오.';
$lang->msg_recaptcha_keys_not_set = 'CAPTCHA Site Key 및 Secret Key를 입력하여 주십시오.';
$lang->msg_recaptcha_test_success = 'CAPTCHA 테스트를 성공적으로 통과했습니다.';

View file

@ -115,6 +115,23 @@ class SpamfilterAdminController extends Spamfilter
$this->setRedirectUrl($returnUrl);
}
public function procSpamfilterAdminSubmitCaptchaTest()
{
$response = Context::get('g-recaptcha-response') ?? Context::get('cf-turnstile-response');
try
{
SpamfilterModel::checkCaptchaResponse($response);
}
catch (Exception $e)
{
return new BaseObject(-1, $e->getMessage());
}
$this->setMessage('msg_recaptcha_test_success');
$this->setRedirectUrl(getNotEncodedUrl('', 'module', 'admin', 'act', 'dispSpamfilterAdminConfigCaptchaTest'));
}
public function procSpamfilterAdminInsertDeniedIP()
{
//스팸IP 추가

View file

@ -86,6 +86,20 @@ class SpamfilterAdminView extends Spamfilter
$this->setTemplateFile('config_captcha');
}
/**
* @brief CAPTCHA Test
*/
public function dispSpamfilterAdminConfigCaptchaTest()
{
$config = ModuleModel::getModuleConfig('spamfilter');
Context::set('config', $config);
$captcha = SpamfilterModel::getCaptcha();
Context::set('captcha', $captcha);
$this->setTemplateFile('captcha_test');
}
}
/* End of file spamfilter.admin.view.php */
/* Location: ./modules/spamfilter/spamfilter.admin.view.php */

View file

@ -218,7 +218,7 @@ class SpamfilterModel extends Spamfilter
{
$config = ModuleModel::getModuleConfig('spamfilter');
$user = Context::get('logged_info');
if (!isset($config) || !isset($config->captcha) || !in_array($config->captcha->type, ['recaptcha', 'turnstile']) || !$config->captcha->site_key || !$config->captcha->secret_key)
if (!isset($config) || empty($config->captcha) || empty($config->captcha->type) || empty($config->captcha->site_key) || empty($config->captcha->secret_key))
{
return false;
}
@ -248,20 +248,58 @@ class SpamfilterModel extends Spamfilter
/**
* Get a CAPTCHA instance.
*
* @return object
* @return ?object
*/
public static function getCaptcha($target_action)
public static function getCaptcha($target_action = null)
{
$config = ModuleModel::getModuleConfig('spamfilter');
$captcha_class = 'Rhymix\\Modules\\Spamfilter\\Captcha\\' . $config->captcha->type;
$captcha_class::init($config->captcha);
if (!isset($config) || empty($config->captcha) || empty($config->captcha->type) || empty($config->captcha->site_key) || empty($config->captcha->secret_key))
{
return null;
}
$captcha_class = 'Rhymix\\Modules\\Spamfilter\\Captcha\\' . $config->captcha->type;
if (!class_exists($captcha_class))
{
return null;
}
$captcha_class::init($config->captcha);
$captcha = new $captcha_class();
if ($target_action)
{
$captcha->setTargetActions([$target_action => true]);
}
$captcha->addScripts();
return $captcha;
}
/**
* Check the CAPTCHA.
*
* This method will throw an exception if the CAPTCHA is invalid.
*
* @param ?string $response
* @return void
*/
public static function checkCaptchaResponse(?string $response = null): void
{
$config = ModuleModel::getModuleConfig('spamfilter');
if (!isset($config) || empty($config->captcha) || empty($config->captcha->type) || empty($config->captcha->site_key) || empty($config->captcha->secret_key))
{
throw new Exception('msg_recaptcha_not_configured');
}
$captcha_class = 'Rhymix\\Modules\\Spamfilter\\Captcha\\' . $config->captcha->type;
if (!class_exists($captcha_class))
{
throw new Exception('msg_recaptcha_not_configured');
}
$captcha_class::init($config->captcha);
$captcha_class::check($response);
}
/**
* @brief Check if the trackbacks have already been registered to a particular article
*/

View file

@ -0,0 +1,32 @@
<config autoescape="on" />
<include target="./header.html" />
<section class="section">
<form action="./" method="post" id="spamfilterConfig" class="x_form-horizontal">
<input type="hidden" name="act" value="procSpamfilterAdminSubmitCaptchaTest" />
<input type="hidden" name="module" value="spamfilter" />
<input type="hidden" name="xe_validator_id" value="modules/spamfilter/tpl/3" />
<div cond="$XE_VALIDATOR_MESSAGE && $XE_VALIDATOR_ID == 'modules/spamfilter/tpl/3'" class="message {$XE_VALIDATOR_MESSAGE_TYPE}">
<p>{$XE_VALIDATOR_MESSAGE}</p>
</div>
<!--@if($captcha)-->
<div class="x_control-group">
<label class="x_control-label" for="captcha_type">{$lang->cmd_captcha_test}</label>
<div class="x_controls">
{$captcha|noescape}
</div>
</div>
<div class="x_clearfix btnArea">
<div class="x_pull-right">
<button type="submit" class="x_btn x_btn-primary">{$lang->cmd_submit}</button>
</div>
</div>
<!--@else-->
<div class="message error">
<p>{$lang->msg_recaptcha_not_configured}</p>
</div>
<!--@end-->
</form>
</section>
<include target="./footer.html" />

View file

@ -11,4 +11,5 @@
<li class="x_active"|cond="$act === 'dispSpamfilterAdminDeniedWordList'"><a href="{getUrl('','module','admin','act','dispSpamfilterAdminDeniedWordList')}">{$lang->cmd_denied_word}</a></li>
<li class="x_active"|cond="$act === 'dispSpamfilterAdminConfigBlock'"><a href="{getUrl('','module','admin','act','dispSpamfilterAdminConfigBlock')}">{$lang->cmd_config_block}</a></li>
<li class="x_active"|cond="$act === 'dispSpamfilterAdminConfigCaptcha'"><a href="{getUrl('','module','admin','act','dispSpamfilterAdminConfigCaptcha')}">{$lang->cmd_captcha_config}</a></li>
<li class="x_active"|cond="$act === 'dispSpamfilterAdminConfigCaptchaTest'"><a href="{getUrl('','module','admin','act','dispSpamfilterAdminConfigCaptchaTest')}">{$lang->cmd_captcha_test}</a></li>
</ul>