Locations and verification
Validate Build / validate-build (push) Failing after 35s
Validate Build / validate-build (push) Failing after 35s
This commit is contained in:
@@ -0,0 +1,85 @@
|
||||
<?php
|
||||
/**
|
||||
* @package ogre-captcha
|
||||
* @author cleverogre
|
||||
* @copyright 2026 CleverOgre
|
||||
* @license GLP-3.0-or-later
|
||||
* @version 0.0.0
|
||||
* @since 0.0.0
|
||||
*/
|
||||
|
||||
namespace Ogre\Captcha;
|
||||
|
||||
use Ogre\Singleton;
|
||||
use Ogre\Captcha as Plugin;
|
||||
use Ogre\Captcha\Settings;
|
||||
use WP_Error;
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
abstract class Location {
|
||||
use Singleton;
|
||||
|
||||
public const ERROR_EMPTY_TOKEN = 'empty_captcha_token';
|
||||
public const ERROR_INVALID_CAPTCHA = 'invalid_captcha_result';
|
||||
|
||||
protected string $name;
|
||||
|
||||
protected function __construct(string $name) {
|
||||
$this->name = $name;
|
||||
add_action('init', [$this, 'init']);
|
||||
}
|
||||
|
||||
public function enabled():bool {
|
||||
return Settings::is_form_location_enabled($this->name);
|
||||
}
|
||||
|
||||
public function init():void {
|
||||
if (is_multisite()) {
|
||||
$blog_id = get_current_blog_id();
|
||||
switch_to_blog($blog_id);
|
||||
}
|
||||
$enabled = $this->enabled();
|
||||
if (is_multisite()) {
|
||||
restore_current_blog();
|
||||
}
|
||||
if (!$enabled) return;
|
||||
|
||||
$this->activate();
|
||||
}
|
||||
|
||||
abstract public function activate():void;
|
||||
|
||||
public function render():void {
|
||||
Widget::render();
|
||||
}
|
||||
|
||||
public function process(WP_Error $errors):void {
|
||||
if (!Settings::is_connected()) return;
|
||||
|
||||
$token = Widget::get_token();
|
||||
if (empty($token)) {
|
||||
$errors->add(self::ERROR_EMPTY_TOKEN, Plugin::__('<strong>Error:</strong> Please complete CAPTCHA verification.'));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!API::verify($token)) {
|
||||
$errors->add(self::ERROR_INVALID_CAPTCHA, Plugin::__('<strong>Error:</strong> Your answer was incorrect - please try again.'));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
protected function print_errors(WP_Error $errors):void {
|
||||
if (!$errors->has_errors()) return;
|
||||
ob_start();
|
||||
echo '<ul>';
|
||||
foreach ($errors->get_error_codes() as $code) {
|
||||
foreach ($errors->get_error_messages($code) as $message) {
|
||||
echo '<li>' . wp_kses($message, ['strong']) . '</li>';
|
||||
}
|
||||
}
|
||||
echo '</ul>';
|
||||
wp_die(ob_get_clean());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
/**
|
||||
* @package ogre-captcha
|
||||
* @author cleverogre
|
||||
* @copyright 2026 CleverOgre
|
||||
* @license GLP-3.0-or-later
|
||||
* @version 0.0.0
|
||||
* @since 0.0.0
|
||||
*/
|
||||
|
||||
namespace Ogre\Captcha;
|
||||
|
||||
use Ogre\Captcha\Settings;
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
final class API {
|
||||
|
||||
private static function transaction(string $url, array $data):array|string|bool {
|
||||
$ch = curl_init($url);
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, wp_json_encode($data));
|
||||
curl_setopt($ch, CURLOPT_HTTPHEADER, [
|
||||
'content-type: application/json',
|
||||
'accept: application/json',
|
||||
]);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
|
||||
$result = curl_exec($ch);
|
||||
if ($result === false || !is_string($result) || empty($result)) return 'Invalid response';
|
||||
|
||||
$result = json_decode($result, true);
|
||||
if (!is_array($result) || is_null($result)) return 'Invalid formatting';
|
||||
|
||||
if (isset($result['error']) && !empty($result['error'])) return $result['error'];
|
||||
if (isset($result['success']) && is_bool($result['success'])) return $result['success'];
|
||||
return $result;
|
||||
}
|
||||
|
||||
private static function get_siteverify_url():string {
|
||||
if (!Settings::is_connected()) return '';
|
||||
return Settings::get_instance_url(Settings::get_site_key() . '/siteverify');
|
||||
}
|
||||
|
||||
public static function verify(string $token):bool {
|
||||
if (empty($token) || !Settings::is_connected()) return false;
|
||||
|
||||
$result = self::transaction(self::get_siteverify_url(), [
|
||||
'secret' => Settings::get_secret_key(),
|
||||
'response' => $token,
|
||||
]);
|
||||
|
||||
return is_bool($result) ? $result : false;
|
||||
}
|
||||
|
||||
}
|
||||
+30
-12
@@ -23,7 +23,7 @@ final class Settings {
|
||||
|
||||
public const LOCATION_LOGIN_PAGE = 'login_page';
|
||||
public const LOCATION_REGISTRATION_PAGE = 'registration_page';
|
||||
public const LOCATION_LOST_PASSWORD_PAGE = 'lost_password_page';
|
||||
public const LOCATION_LOST_PASSWORD_FORM = 'lost_password_page';
|
||||
public const LOCATION_LOGIN_FORM = 'login_form';
|
||||
public const LOCATION_COMMENT_FORM = 'comment_form';
|
||||
public const LOCATION_POST_PASSWORD_FORM = 'post_password_form';
|
||||
@@ -43,8 +43,18 @@ final class Settings {
|
||||
return in_array($value, (array) self::get($name));
|
||||
}
|
||||
|
||||
public static function get_instance_url():string {
|
||||
return !empty($value = (string) self::get('instance_url')) ? $value : self::DEFAULT_INSTANCE_URL;
|
||||
public static function get_instance_url(string $path = ''):string {
|
||||
$url = (string) self::get('instance_url');
|
||||
if (empty($url)) $url = self::DEFAULT_INSTANCE_URL;
|
||||
if (!wp_http_validate_url($url)) return '';
|
||||
if (!empty($path)) {
|
||||
$url = sprintf(
|
||||
'%s/%s',
|
||||
untrailingslashit(Settings::get_instance_url()),
|
||||
$path
|
||||
);
|
||||
}
|
||||
return $url;
|
||||
}
|
||||
|
||||
public static function get_site_key():string {
|
||||
@@ -55,32 +65,40 @@ final class Settings {
|
||||
return (string) self::get('secret_key');
|
||||
}
|
||||
|
||||
public static function is_connected():bool {
|
||||
return !empty(self::get_site_key()) && !empty(self::get_secret_key());
|
||||
}
|
||||
|
||||
public static function get_form_locations():array {
|
||||
return (array) self::get('form_locations');
|
||||
}
|
||||
|
||||
public static function is_form_location_enabled(string $name):bool {
|
||||
return in_array($name, self::get_form_locations());
|
||||
}
|
||||
|
||||
public static function is_login_page_enabled():bool {
|
||||
return in_array(self::LOCATION_LOGIN_PAGE, self::get_form_locations());
|
||||
return self::is_form_location_enabled(self::LOCATION_LOGIN_PAGE);
|
||||
}
|
||||
|
||||
public static function is_registration_page_enabled():bool {
|
||||
return in_array(self::LOCATION_REGISTRATION_PAGE, self::get_form_locations());
|
||||
return self::is_form_location_enabled(self::LOCATION_REGISTRATION_PAGE);
|
||||
}
|
||||
|
||||
public static function is_lost_password_page_enabled():bool {
|
||||
return in_array(self::LOCATION_LOST_PASSWORD_PAGE, self::get_form_locations());
|
||||
public static function is_lost_password_form_enabled():bool {
|
||||
return self::is_form_location_enabled(self::LOCATION_LOST_PASSWORD_FORM);
|
||||
}
|
||||
|
||||
public static function is_login_form_enabled():bool {
|
||||
return in_array(self::LOCATION_LOGIN_FORM, self::get_form_locations());
|
||||
return self::is_form_location_enabled(self::LOCATION_LOGIN_FORM);
|
||||
}
|
||||
|
||||
public static function is_comment_form_enabled():bool {
|
||||
return in_array(self::LOCATION_COMMENT_FORM, self::get_form_locations());
|
||||
return self::is_form_location_enabled(self::LOCATION_COMMENT_FORM);
|
||||
}
|
||||
|
||||
public static function is_post_password_form_enabled():bool {
|
||||
return in_array(self::LOCATION_POST_PASSWORD_FORM, self::get_form_locations());
|
||||
return self::is_form_location_enabled(self::LOCATION_POST_PASSWORD_FORM);
|
||||
}
|
||||
|
||||
public function menu() {
|
||||
@@ -156,8 +174,8 @@ final class Settings {
|
||||
'options' => [
|
||||
self::LOCATION_LOGIN_PAGE => Plugin::__('Login page'),
|
||||
self::LOCATION_REGISTRATION_PAGE => Plugin::__('Registration page'),
|
||||
self::LOCATION_LOST_PASSWORD_PAGE => Plugin::__('Lost password page'),
|
||||
self::LOCATION_LOGIN_FORM => Plugin::__('Custom login form'),
|
||||
self::LOCATION_LOST_PASSWORD_FORM => Plugin::__('Lost password page'),
|
||||
self::LOCATION_LOGIN_FORM => Plugin::__('Custom login form (requires Login Page)'),
|
||||
self::LOCATION_COMMENT_FORM => Plugin::__('Comment form'),
|
||||
self::LOCATION_POST_PASSWORD_FORM => Plugin::__('Password-protected posts and pages'),
|
||||
],
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
/**
|
||||
* @package ogre-captcha
|
||||
* @author cleverogre
|
||||
* @copyright 2026 CleverOgre
|
||||
* @license GLP-3.0-or-later
|
||||
* @version 0.0.0
|
||||
* @since 0.0.0
|
||||
*/
|
||||
|
||||
namespace Ogre\Captcha;
|
||||
|
||||
use Ogre\Captcha\Settings;
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
final class Widget {
|
||||
|
||||
private const SCRIPT_HANDLE = 'cap';
|
||||
private const TOKEN_NAME = 'cap-token';
|
||||
|
||||
private static function get_script_url():string {
|
||||
if (!Settings::is_connected()) return '';
|
||||
return Settings::get_instance_url('assets/widget.js');
|
||||
}
|
||||
|
||||
public static function enqueue_script():void {
|
||||
if (!Settings::is_connected() || wp_script_is(self::SCRIPT_HANDLE)) return;
|
||||
wp_enqueue_script(self::SCRIPT_HANDLE, self::get_script_url());
|
||||
}
|
||||
|
||||
public static function render(string $id = 'cap'):void {
|
||||
if (!Settings::is_connected()) return;
|
||||
self::enqueue_script();
|
||||
printf(
|
||||
'<cap-widget id="%s" required data-cap-api-endpoint="%s"></cap-widget>',
|
||||
esc_attr($id),
|
||||
esc_url(Settings::get_instance_url(Settings::get_site_key() . '/'))
|
||||
);
|
||||
}
|
||||
|
||||
public static function get_token():string {
|
||||
return isset($_POST[self::TOKEN_NAME]) ? (string) $_POST[self::TOKEN_NAME] : '';
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
/**
|
||||
* @package ogre-captcha
|
||||
* @author cleverogre
|
||||
* @copyright 2026 CleverOgre
|
||||
* @license GLP-3.0-or-later
|
||||
* @version 0.0.0
|
||||
* @since 0.0.0
|
||||
*/
|
||||
|
||||
namespace Ogre\Captcha\Location;
|
||||
|
||||
use Override;
|
||||
use Ogre\Captcha\Location;
|
||||
use Ogre\Captcha\Settings;
|
||||
use WP_Error;
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
final class CommentForm extends Location {
|
||||
|
||||
protected function __construct() {
|
||||
parent::__construct(Settings::LOCATION_COMMENT_FORM);
|
||||
}
|
||||
|
||||
#[Override]
|
||||
public function activate(): void
|
||||
{
|
||||
add_action('comment_form_after_fields', [$this, 'render'], 1);
|
||||
add_filter('preprocess_comment', [$this, 'process_form']);
|
||||
}
|
||||
|
||||
public function process_form(array $commentdata):array {
|
||||
// Don't process CAPTCHA for comment replies inside admin menu
|
||||
if (isset($_REQUEST['action']) && 'replyto-comment' == $_REQUEST['action'] && (check_ajax_referer('replyto-comment', '_ajax_nonce', false) || check_ajax_referer('replyto-comment', '_ajax_nonce-replyto-comment', false))) {
|
||||
return $comment;
|
||||
}
|
||||
|
||||
// Don't do CAPTCHA for pingback/trackback
|
||||
if ('' != $comment['comment_type'] && 'comment' != $comment['comment_type'] && 'review' != $comment['comment_type']) {
|
||||
return $comment;
|
||||
}
|
||||
|
||||
$errors = new WP_Error();
|
||||
$this->process($errors);
|
||||
$this->print_errors($errors);
|
||||
|
||||
return $commentdata;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
CommentForm::instance();
|
||||
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
/**
|
||||
* @package ogre-captcha
|
||||
* @author cleverogre
|
||||
* @copyright 2026 CleverOgre
|
||||
* @license GLP-3.0-or-later
|
||||
* @version 0.0.0
|
||||
* @since 0.0.0
|
||||
*/
|
||||
|
||||
namespace Ogre\Captcha\Location;
|
||||
|
||||
use Override;
|
||||
use Ogre\Captcha\Location;
|
||||
use Ogre\Captcha\Settings;
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
final class LoginForm extends Location {
|
||||
|
||||
protected function __construct() {
|
||||
parent::__construct(Settings::LOCATION_LOGIN_FORM);
|
||||
}
|
||||
|
||||
#[Override]
|
||||
public function enabled(): bool
|
||||
{
|
||||
return is_user_logged_in() ? false : parent::enabled();
|
||||
}
|
||||
|
||||
#[Override]
|
||||
public function activate(): void
|
||||
{
|
||||
add_filter('login_form_middle', [$this, 'render_form'], 10, 2);
|
||||
// NOTE: Requires login page processing
|
||||
}
|
||||
|
||||
public function render_form(string $content, array $args):string {
|
||||
ob_start();
|
||||
$this->render();
|
||||
$content .= ob_get_clean();
|
||||
return $content;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
LoginForm::instance();
|
||||
@@ -0,0 +1,61 @@
|
||||
<?php
|
||||
/**
|
||||
* @package ogre-captcha
|
||||
* @author cleverogre
|
||||
* @copyright 2026 CleverOgre
|
||||
* @license GLP-3.0-or-later
|
||||
* @version 0.0.0
|
||||
* @since 0.0.0
|
||||
*/
|
||||
|
||||
namespace Ogre\Captcha\Location;
|
||||
|
||||
use Override;
|
||||
use Ogre\Captcha\Location;
|
||||
use Ogre\Captcha\Settings;
|
||||
use WP_Error;
|
||||
use WP_User;
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
final class LoginPage extends Location {
|
||||
|
||||
protected function __construct() {
|
||||
parent::__construct(Settings::LOCATION_LOGIN_PAGE);
|
||||
}
|
||||
|
||||
#[Override]
|
||||
public function enabled(): bool
|
||||
{
|
||||
return is_user_logged_in() ? false : parent::enabled();
|
||||
}
|
||||
|
||||
#[Override]
|
||||
public function activate(): void
|
||||
{
|
||||
add_action('login_form', [$this, 'render']);
|
||||
add_filter('shake_error_codes', [$this, 'shake_error_codes']);
|
||||
add_filter('authenticate', [$this, 'process_form'], 20, 1);
|
||||
}
|
||||
|
||||
public function shake_error_codes(array $shake_error_codes):array {
|
||||
return array_merge(
|
||||
$shake_error_codes,
|
||||
[
|
||||
self::ERROR_EMPTY_TOKEN,
|
||||
self::ERROR_INVALID_CAPTCHA,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
public function process_form(WP_User|WP_Error $user):WP_User|WP_Error {
|
||||
if (is_wp_error($user)) return $user;
|
||||
if (!isset($_POST['log']) || !isset($_POST['pwd'])) return $user;
|
||||
$errors = new WP_Error();
|
||||
$this->process($errors);
|
||||
return $errors->has_errors() ? $errors : $user;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
LoginPage::instance();
|
||||
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
/**
|
||||
* @package ogre-captcha
|
||||
* @author cleverogre
|
||||
* @copyright 2026 CleverOgre
|
||||
* @license GLP-3.0-or-later
|
||||
* @version 0.0.0
|
||||
* @since 0.0.0
|
||||
*/
|
||||
|
||||
namespace Ogre\Captcha\Location;
|
||||
|
||||
use Override;
|
||||
use Ogre\Captcha\Location;
|
||||
use Ogre\Captcha\Settings;
|
||||
use WP_Error;
|
||||
use WP_User;
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
final class LostPasswordForm extends Location {
|
||||
|
||||
protected function __construct() {
|
||||
parent::__construct(Settings::LOCATION_LOST_PASSWORD_FORM);
|
||||
}
|
||||
|
||||
#[Override]
|
||||
public function enabled(): bool
|
||||
{
|
||||
return is_user_logged_in() ? false : parent::enabled();
|
||||
}
|
||||
|
||||
#[Override]
|
||||
public function activate(): void
|
||||
{
|
||||
add_action('lostpassword_form', [$this, 'render']);
|
||||
add_action('lostpassword_post', [$this, 'process_form']);
|
||||
}
|
||||
|
||||
public function process_form(WP_Error $errors, WP_User|bool $user_data):void {
|
||||
$this->process($errors);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
LostPasswordForm::instance();
|
||||
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
/**
|
||||
* @package ogre-captcha
|
||||
* @author cleverogre
|
||||
* @copyright 2026 CleverOgre
|
||||
* @license GLP-3.0-or-later
|
||||
* @version 0.0.0
|
||||
* @since 0.0.0
|
||||
*/
|
||||
|
||||
namespace Ogre\Captcha\Location;
|
||||
|
||||
use Override;
|
||||
use Ogre\Captcha\Location;
|
||||
use Ogre\Captcha\Settings;
|
||||
use WP_Error;
|
||||
use WP_Post;
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
final class PostPasswordForm extends Location {
|
||||
|
||||
protected function __construct() {
|
||||
parent::__construct(Settings::LOCATION_POST_PASSWORD_FORM);
|
||||
}
|
||||
|
||||
#[Override]
|
||||
public function activate(): void
|
||||
{
|
||||
add_filter('the_password_form', [$this, 'render_form']);
|
||||
add_action('login_form_postpass', [$this, 'process_form']);
|
||||
}
|
||||
|
||||
public function render_form(string $output, WP_Post $post, string $invalid_password):string {
|
||||
ob_start();
|
||||
$this->render();
|
||||
$output .= ob_get_clean();
|
||||
return $output;
|
||||
}
|
||||
|
||||
public function process_form():void {
|
||||
$errors = new WP_Error();
|
||||
$this->process($errors);
|
||||
$this->print_errors($errors);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
PostPasswordForm::instance();
|
||||
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
/**
|
||||
* @package ogre-captcha
|
||||
* @author cleverogre
|
||||
* @copyright 2026 CleverOgre
|
||||
* @license GLP-3.0-or-later
|
||||
* @version 0.0.0
|
||||
* @since 0.0.0
|
||||
*/
|
||||
|
||||
namespace Ogre\Captcha\Location;
|
||||
|
||||
use Override;
|
||||
use Ogre\Captcha\Location;
|
||||
use Ogre\Captcha\Settings;
|
||||
use WP_Error;
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
final class RegistrationPage extends Location {
|
||||
|
||||
protected function __construct() {
|
||||
parent::__construct(Settings::LOCATION_REGISTRATION_PAGE);
|
||||
}
|
||||
|
||||
#[Override]
|
||||
public function enabled(): bool
|
||||
{
|
||||
return is_user_logged_in() ? false : parent::enabled();
|
||||
}
|
||||
|
||||
#[Override]
|
||||
public function activate(): void
|
||||
{
|
||||
if (is_multisite()) {
|
||||
add_action('signup_extra_fields', [$this, 'render']);
|
||||
add_filter('wpmu_validate_user_signup', [$this, 'process_multisite_form']);
|
||||
} else {
|
||||
add_action('register_form', [$this, 'render']);
|
||||
}
|
||||
add_filter('registration_errors', [$this, 'process_form'], 10, 3);
|
||||
}
|
||||
|
||||
public function process_multisite_form(array $result):array {
|
||||
$this->process($result['errors']);
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function process_form(WP_Error $errors, string $sanitized_user_login, string $user_email):WP_Error {
|
||||
$this->process($errors);
|
||||
return $errors;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
RegistrationPage::instance();
|
||||
+10
-1
@@ -1,6 +1,6 @@
|
||||
<?php
|
||||
/**
|
||||
* Plugin
|
||||
* Ogre CAPTCHA
|
||||
*
|
||||
* @package ogre-captcha
|
||||
* @author cleverogre
|
||||
@@ -40,6 +40,15 @@ final class Captcha extends Plugin {
|
||||
|
||||
$this->add_files([
|
||||
'inc/class-settings.php',
|
||||
'inc/class-api.php',
|
||||
'inc/class-widget.php',
|
||||
'inc/abstract-location.php',
|
||||
'inc/location/class-comment-form.php',
|
||||
'inc/location/class-login-form.php',
|
||||
'inc/location/class-login-page.php',
|
||||
'inc/location/class-lost-password-form.php',
|
||||
'inc/location/class-post-password-form.php',
|
||||
'inc/location/class-registration-page.php',
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user