13 Commits

Author SHA1 Message Date
ogrecooper 37fb1e0cc1 Merge pull request 'Add WP-CLI support' (#6) from cli into main
Reviewed-on: #6
2026-06-09 21:30:01 +00:00
Cooper Dalrymple 90cbfc2c02 Update to version 1.2.1 2026-06-09 16:29:13 -05:00
Cooper Dalrymple 97ec73035e Add cli commands for settings 2026-06-09 16:29:06 -05:00
ogrecooper c37712d2b5 Merge pull request 'Add floating widget support' (#5) from floating into main
Reviewed-on: #5
2026-06-09 18:41:10 +00:00
Cooper Dalrymple 3bcedb614b Fix floating target button tag 2026-06-09 13:39:43 -05:00
Cooper Dalrymple 2826db7951 Fix dynamic floating widget 2026-06-09 13:27:41 -05:00
Cooper Dalrymple cc135e0401 Fix floating widget in post password form 2026-06-09 13:19:51 -05:00
Cooper Dalrymple 6ced723e4f Fix lost password form 2026-06-09 13:09:33 -05:00
Cooper Dalrymple 8159c4a46c Add simple select all option to forms settings 2026-06-09 13:04:40 -05:00
Cooper Dalrymple 79f95fa0df Update version to 1.2.0 2026-06-09 12:56:06 -05:00
Cooper Dalrymple 6a6e8f3960 Add floating mode support 2026-06-09 12:53:19 -05:00
Cooper Dalrymple 401676e7f3 Fix changelog 2026-06-09 12:19:49 -05:00
Cooper Dalrymple 0e63494eec Add missing namespace 2026-06-08 15:48:08 -05:00
20 changed files with 680 additions and 46 deletions
+26
View File
@@ -0,0 +1,26 @@
/**
* @package ogre-captcha
* @author cleverogre
* @copyright 2026 CleverOgre
* @license GLP-3.0-or-later
* @version 1.2.0
* @since 1.2.0
*/
(() => {
document.addEventListener('DOMContentLoaded', () => {
if (typeof window['CAP_FLOATING_DYNAMIC'] === 'undefined') return;
const element = document.body.querySelector(CAP_FLOATING_DYNAMIC['selector']);
if (element === null) return;
for (const [name, value] of Object.entries(CAP_FLOATING_DYNAMIC['attributes'])) {
element.setAttribute(name, value);
}
// Trigger MutationObserver
const parent = element.parentElement;
parent.removeChild(element);
parent.appendChild(element);
});
})();
+1 -1
View File
@@ -1,7 +1,7 @@
{
"$schema": "https://getcomposer.org/schema.json",
"name": "cleverogre/ogre-captcha",
"version": "1.1.2",
"version": "1.2.1",
"title": "Ogre CAPTCHA",
"description": "Protect your site with CAPTCHA",
"author": "CleverOgre",
+52 -2
View File
@@ -4,15 +4,15 @@
* @author cleverogre
* @copyright 2026 CleverOgre
* @license GLP-3.0-or-later
* @version 1.0.0
* @version 1.2.0
* @since 1.0.0
*/
namespace Ogre\Captcha;
use Ogre\Singleton;
use Ogre\Captcha as Plugin;
use Ogre\Captcha\Settings;
use Override;
use WP_Error;
defined('ABSPATH') || exit;
@@ -70,4 +70,54 @@ abstract class Location {
wp_die(ob_get_clean());
}
public function header():void {
if (!Settings::is_widget_floating()) return;
ob_start();
}
public function footer():void {
if (!Settings::is_widget_floating()) return;
$html = ob_get_clean();
$html = Widget::set_button_floating($html);
echo $html;
}
}
abstract class LoginLocation extends Location {
protected string $action;
protected function __construct(string $name, string $action) {
$this->action = $action;
parent::__construct($name);
}
#[Override]
public function activate(): void
{
add_action('login_head', [$this, 'header']);
add_action('login_footer', [$this, 'footer']);
}
private function get_action():string {
return isset($_REQUEST['action']) && is_string($_REQUEST['action']) ? $_REQUEST['action'] : 'login';
}
protected function is_action():bool {
return $this->get_action() == $this->action;
}
#[Override]
public function header():void {
if (!$this->is_action()) return;
parent::header();
}
#[Override]
public function footer():void {
if (!$this->is_action()) return;
parent::footer();
}
}
+194
View File
@@ -0,0 +1,194 @@
<?php
/**
* @package ogre-captcha
* @author cleverogre
* @copyright 2026 CleverOgre
* @license GLP-3.0-or-later
* @version 1.2.1
* @since 1.2.1
*/
namespace Ogre\Captcha\CLI;
use Ogre\Captcha as Plugin;
use Ogre\Captcha\GravityForms\Addon as GFAddon;
use Ogre\Captcha\Settings;
use WP_CLI;
defined('ABSPATH') || exit;
final class CLI {
/**
* Set Cap API instance url.
*
* ## OPTIONS
*
* <value>
* : The url of the Cap instance.
*
* ## EXAMPLES
*
* wp ogre-captcha instance_url https://captcha.cleverogre.com
*
* @when after_wp_load
*/
public function instance_url(array $args, array $assoc_args):void {
list($value) = $args;
if (Settings::set_instance_url($value)) {
WP_CLI::success(Plugin::__('Successfully updated api instance url'));
} else {
WP_CLI::error(Plugin::__('Invalid url'));
}
}
/**
* Set Cap API site key.
*
* ## OPTIONS
*
* <value>
* : The value of the site key. Typically 10 characters.
*
* ## EXAMPLES
*
* wp ogre-captcha site_key ##########
*
* @when after_wp_load
*/
public function site_key(array $args, array $assoc_args):void {
list($value) = $args;
if (Settings::set_site_key($value)) {
WP_CLI::success(Plugin::__('Successfully updated api site key'));
} else {
WP_CLI::error(Plugin::__('Invalid site key'));
}
}
/**
* Set Cap API secret key.
*
* ## OPTIONS
*
* <value>
* : The value of the secret key. Typically 43 characters.
*
* ## EXAMPLES
*
* wp ogre-captcha secret_key sk-########################################
*
* @when after_wp_load
*/
public function secret_key(array $args, array $assoc_args):void {
list($value) = $args;
if (Settings::set_secret_key($value)) {
WP_CLI::success(Plugin::__('Successfully updated api secret key'));
} else {
WP_CLI::error(Plugin::__('Invalid secret key'));
}
}
/**
* Enable (or disable) CAPTCHA on the designated forms.
*
* ## OPTIONS
*
* [<locations>...]
* : The form locations to alter. The following values are accepted. "login_page", "registration_page", "lost_password_page", "login_form", "comment_form", "post_password_form", "wc_login", "wc_lost_password", "wc_register", "wc_checkout"
*
* [--all]
* : Whether or not to include all possible form locations.
*
* [--disable]
* : Whether or not to disable the designated form locations rather than enable.
*
* ## EXAMPLES
*
* wp ogre-captcha form --all
*
* @when after_wp_load
*/
public function form(array $args, array $assoc_args):void {
list($locations) = $args;
extract($assoc_args);
if (empty($locations) && !$all) {
WP_CLI::error('Please specify one or more form locations, or use --all.');
return;
}
if (!!$all) $locations = Settings::ALL_LOCATIONS;
$locations = array_filter($locations, fn ($location) => !Settings::set_form_location($location, !$disable));
if (empty($locations)) {
WP_CLI::success(Plugin::__('Successfully updated all form locations'));
} else {
WP_CLI::error(
Plugin::__('Failed to alter form locations: %s'),
implode(', ', $locations)
);
}
}
/**
* Set Cap widget type.
*
* ## OPTIONS
*
* <value>
* : The value of the widget type. Either "standard" or "floating".
*
* ## EXAMPLES
*
* wp ogre-captcha widget_type floating
*
* @when after_wp_load
*/
public function widget_type(array $args, array $assoc_args):void {
list($value) = $args;
if (Settings::set_widget_type($value)) {
WP_CLI::success(Plugin::__('Successfully updated widget type'));
} else {
WP_CLI::error(Plugin::__('Invalid widget type'));
}
}
/**
* Enable or disable CAPTCHA on Gravity Forms.
*
* ## OPTIONS
*
* <value>
* : Whether or not to enable CAPTCHA on all forms by default.
* ---
* options:
* - enable
* - disable
* ---
*
* ## EXAMPLES
*
* wp ogre-captcha gravityforms enable
*
* @when gform_loaded
*/
public function gravityforms(array $args, array $assoc_args):void {
list($value) = $args;
$value = $value == 'enable';
if (!Plugin::is_gravityforms_active()) {
WP_CLI::error(Plugin::__('Gravity Forms not active'));
return;
}
if (GFAddon::get_instance()?->set_enabled($value)) {
WP_CLI::success(Plugin::__('Successfully updated Gravity Forms integration'));
} else {
WP_CLI::error(Plugin::__('Unable to update setting'));
}
}
}
WP_CLI::add_command(Plugin::get_id(), CLI::class);
+143 -8
View File
@@ -4,7 +4,7 @@
* @author cleverogre
* @copyright 2026 CleverOgre
* @license GLP-3.0-or-later
* @version 1.0.0
* @version 1.2.1
* @since 1.0.0
*/
@@ -27,11 +27,45 @@ final class Settings {
public const LOCATION_LOGIN_FORM = 'login_form';
public const LOCATION_COMMENT_FORM = 'comment_form';
public const LOCATION_POST_PASSWORD_FORM = 'post_password_form';
public const WP_LOCATIONS = [
self::LOCATION_LOGIN_PAGE,
self::LOCATION_REGISTRATION_PAGE,
self::LOCATION_LOST_PASSWORD_FORM,
self::LOCATION_LOGIN_FORM,
self::LOCATION_COMMENT_FORM,
self::LOCATION_POST_PASSWORD_FORM,
];
public const LOCATION_WC_LOGIN = 'wc_login';
public const LOCATION_WC_LOST_PASSWORD = 'wc_lost_password';
public const LOCATION_WC_REGISTER = 'wc_register';
public const LOCATION_WC_CHECKOUT = 'wc_checkout';
public const WC_LOCATIONS = [
self::LOCATION_WC_LOGIN,
self::LOCATION_WC_LOST_PASSWORD,
self::LOCATION_WC_REGISTER,
self::LOCATION_WC_CHECKOUT,
];
public const ALL_LOCATIONS = [
self::LOCATION_LOGIN_PAGE,
self::LOCATION_REGISTRATION_PAGE,
self::LOCATION_LOST_PASSWORD_FORM,
self::LOCATION_LOGIN_FORM,
self::LOCATION_COMMENT_FORM,
self::LOCATION_POST_PASSWORD_FORM,
self::LOCATION_WC_LOGIN,
self::LOCATION_WC_LOST_PASSWORD,
self::LOCATION_WC_REGISTER,
self::LOCATION_WC_CHECKOUT,
];
public const WIDGET_TYPE_STANDARD = 'standard';
public const WIDGET_TYPE_FLOATING = 'floating';
public const WIDGET_TYPES = [
self::WIDGET_TYPE_STANDARD,
self::WIDGET_TYPE_FLOATING,
];
protected function __construct() {
add_action('admin_menu', [$this, 'menu']);
@@ -44,6 +78,10 @@ final class Settings {
return $data;
}
private static function has(string $key):bool {
return array_key_exists($key, self::get());
}
private static function set(string $key, $value = null):void {
$data = self::get();
if (is_null($value) && isset($data[$key])) {
@@ -54,7 +92,8 @@ final class Settings {
update_option(Plugin::get_id(), $data);
}
private static function is_checked(string $name, string $value):bool {
private static function is_checked(string $name, string $value, string|null $default = null):bool {
if (!is_null($default) && !self::has($name)) return $value == $default;
return in_array($value, (array) self::get($name));
}
@@ -72,14 +111,30 @@ final class Settings {
return $url;
}
public static function set_instance_url(string $value):bool {
if (!wp_http_validate_url($value)) return false;
self::set('instance_url', $value);
return true;
}
public static function get_site_key():string {
return (string) self::get('site_key');
}
public static function set_site_key(string $value):bool {
self::get('site_key');
return true;
}
public static function get_secret_key():string {
return (string) self::get('secret_key');
}
public static function set_secret_key(string $value):bool {
self::get('secret_key');
return true;
}
public static function is_connected():bool {
return !empty(self::get_site_key()) && !empty(self::get_secret_key());
}
@@ -99,6 +154,26 @@ final class Settings {
return in_array($name, self::get_form_locations());
}
public static function set_form_location(string $name, bool $enabled):bool {
if (in_array($name, self::WP_LOCATIONS)) {
$key = 'form_locations';
} else if (in_array($name, self::WC_LOCATIONS)) {
$key = 'wc_form_locations';
} else {
return false;
}
$value = (array) self::get($key);
if (!!$enabled && !in_array($name, $value)) {
$value[] = $name;
} else if (!$enabled && in_array($name, $value)) {
unset($value[array_search($name, $value)]);
}
self::set($key, $value);
return true;
}
public static function is_login_page_enabled():bool {
return self::is_form_location_enabled(self::LOCATION_LOGIN_PAGE);
}
@@ -139,6 +214,23 @@ final class Settings {
return self::is_form_location_enabled(self::LOCATION_WC_CHECKOUT);
}
public static function get_widget_type():string {
$value = self::get('widget_type');
if (is_array($value) && !empty($value)) $value = array_values($value)[0];
if (!is_string($value)) $value = '';
return in_array($value, self::WIDGET_TYPES) ? $value : self::WIDGET_TYPE_STANDARD;
}
public static function is_widget_floating():bool {
return self::get_widget_type() === self::WIDGET_TYPE_FLOATING;
}
public static function set_widget_type(string $value):bool {
if (!in_array($value, self::WIDGET_TYPES)) return false;
self::set('widget_type', $value);
return true;
}
public function menu() {
if (!current_user_can(self::CAPABILITY)) return;
add_options_page(
@@ -204,11 +296,11 @@ final class Settings {
add_settings_section(
'forms',
Plugin::__('CAPTCHA Forms'),
Plugin::__('Forms'),
[$this, 'section'],
Plugin::get_id(),
[
'description' => Plugin::__('Define which forms you would like to secure with CAPTCHA.'),
'description' => Plugin::__('Define which forms you would like to secure with Cap.'),
]
);
@@ -228,6 +320,7 @@ final class Settings {
self::LOCATION_COMMENT_FORM => Plugin::__('Comment form'),
self::LOCATION_POST_PASSWORD_FORM => Plugin::__('Password-protected posts and pages'),
],
'select_all' => true,
'description' => Plugin::__('Custom login form requires login page to also be enabled.'),
]
);
@@ -247,9 +340,40 @@ final class Settings {
self::LOCATION_WC_REGISTER => Plugin::__('Register form'),
self::LOCATION_WC_CHECKOUT => Plugin::__('Checkout form'),
],
'select_all' => true,
]
);
}
add_settings_section(
'widget',
Plugin::__('Widget'),
[$this, 'section'],
Plugin::get_id(),
[
'description' => Plugin::__('Change how the Cap widget is displayed on the frontend.'),
]
);
add_settings_field(
Plugin::get_id() . '_widget_type',
Plugin::__('Widget type'),
[$this, 'checkbox_field'],
Plugin::get_id(),
'widget',
[
'type' => 'radio',
'name' => 'widget_type',
'options' => [
self::WIDGET_TYPE_STANDARD => Plugin::__('Standard (default)'),
self::WIDGET_TYPE_FLOATING => Plugin::__('Floating'),
],
'default' => self::WIDGET_TYPE_STANDARD,
'description' => Plugin::__('Not sure which type you want to use? <a href="https://trycap.dev/guide/demo.html" target="_blank">View the demo</a> to try them out.'),
]
);
}
public function page() {
@@ -332,13 +456,24 @@ final class Settings {
public function checkbox_field(array $args):void {
if (empty($args['name'] ?? '') || empty($args['options'] ?? [])) return;
echo implode('<br>', array_map(
fn (string $name, string $label):string => sprintf(
'<label><input type="checkbox" name="%s[%s][]" value="%s" %s>&nbsp;%s</label>',
if (!!($args['select_all'] ?? false) && ($args['type'] ?? 'checkbox') == 'checkbox') {
printf(
'<label><input type="checkbox" onClick="document.getElementsByName(\'%s[%s][]\').forEach(element => { element.checked = this.checked });">&nbsp;%s</label><br>',
esc_attr(Plugin::get_id()),
esc_attr($args['name']),
Plugin::esc_html__('Select All')
);
}
echo implode('<br>', array_map(
fn (string $name, string $label):string => sprintf(
'<label><input type="%s" name="%s[%s]%s" value="%s" %s>&nbsp;%s</label>',
esc_attr($args['type'] ?? 'checkbox'),
esc_attr(Plugin::get_id()),
esc_attr($args['name']),
(($args['type'] ?? 'checkbox') ? '[]' : ''),
esc_attr($name),
checked(self::is_checked($args['name'], $name), display: false),
checked(self::is_checked($args['name'], $name, $args['default'] ?? null), display: false),
esc_html($label)
),
array_keys($args['options']),
+166 -7
View File
@@ -4,7 +4,7 @@
* @author cleverogre
* @copyright 2026 CleverOgre
* @license GLP-3.0-or-later
* @version 1.1.2
* @version 1.2.0
* @since 1.0.0
*/
@@ -12,12 +12,15 @@ namespace Ogre\Captcha;
use Ogre\Captcha as Plugin;
use Ogre\Captcha\Settings;
use WP_HTML_Tag_Processor;
defined('ABSPATH') || exit;
final class Widget {
private const HANDLE = 'cap';
private const FLOATING_HANDLE = 'cap-floating';
private const DYNAMIC_FLOATING_HANDLE = 'cap-floating-dynamic';
private const TOKEN_NAME = 'cap-token';
private static function get_script_url():string {
@@ -25,28 +28,184 @@ final class Widget {
return Settings::get_instance_url('assets/widget.js');
}
public static function enqueue_assets():void {
private static function get_floating_script_url():string {
if (!Settings::is_connected()) return '';
return Settings::get_instance_url('assets/floating.js');
}
public static function enqueue_assets(bool $floating = false):void {
if (!Settings::is_connected()) return;
if (!wp_script_is(self::HANDLE)) {
wp_enqueue_script(self::HANDLE, self::get_script_url());
wp_enqueue_script(
self::HANDLE,
self::get_script_url(),
args: [
'in_footer' => true
]
);
}
if ($floating && !wp_script_is(self::FLOATING_HANDLE)) {
wp_enqueue_script(
self::FLOATING_HANDLE,
self::get_floating_script_url(),
[
self::HANDLE
],
args: [
'in_footer' => true
]
);
}
if (!wp_style_is(self::HANDLE)) {
wp_enqueue_style(self::HANDLE, Plugin::get_url('assets/widget.css'));
wp_enqueue_style(
self::HANDLE,
Plugin::get_url('assets/widget.css')
);
}
}
public static function render(string $id = 'cap'):void {
public static function render(array $args = []):void {
if (!Settings::is_connected()) return;
self::enqueue_assets();
$args = wp_parse_args(
$args,
[
'id' => 'cap',
'floating' => Settings::is_widget_floating(),
]
);
self::enqueue_assets(!!$args['floating']);
printf(
'<cap-widget id="%s" required data-cap-api-endpoint="%s"></cap-widget>',
esc_attr($id),
esc_attr(!empty($args['id']) ? strval($args['id']) : 'cap'),
esc_url(Settings::get_instance_url(Settings::get_site_key() . '/'))
);
}
public static function get_button_floating_attributes(array $args = []):array {
$args = wp_parse_args(
$args,
[
'id' => 'cap',
'position' => 'bottom',
'offset' => null,
]
);
$attributes = [
'data-cap-floating' => "#{$args['id']}",
'data-cap-floating-position' => $args['position'],
];
if (!is_null($args['offset'])) {
$attributes['data-cap-floating-offset'] = $args['offset'];
}
return $attributes;
}
private static function parse_button_floating_args(array $args):array {
return wp_parse_args(
$args,
[
'id' => 'cap',
'position' => 'bottom',
'offset' => null,
'tag_name' => null,
'class_name' => null,
'type_name' => 'submit',
]
);
}
private static function get_button_floating_selector(array $args):string {
$selector = '';
if (!is_null($args['tag_name']) && !empty($args['tag_name'])) {
$selector = $args['tag_name'];
} else {
$selector = ':where(input, button)';
}
if (!is_null($args['class_name']) && !empty($args['class_name'])) {
$selector = sprintf(
'.',
sanitize_html_class($args['class_name'])
);
}
if (!is_null($args['type_name']) && !empty($args['type_name'])) {
$selector .= sprintf(
'[type="%s"]',
esc_attr($args['type_name'])
);
}
return $selector;
}
public static function set_button_floating(string $html, array $args = []):string {
if (empty($html)) return $html;
$args = self::parse_button_floating_args($args);
$attributes = self::get_button_floating_attributes($args);
$queries = ['input', 'button'];
if (!is_null($args['tag_name']) || !is_null($args['class_name'])) {
$queries = [[
'tag_name' => $args['tag_name'],
'class_name' => $args['class_name'],
]];
}
$tags = new WP_HTML_Tag_Processor($html);
$tags->next_tag();
$tags->set_bookmark('top');
foreach ($queries as $query) {
$tags->seek('top');
$found = false;
while ($tags->next_tag($query)) {
if (!is_null($args['type_name']) && !empty($args['type_name']) && $tags->get_attribute('type') !== $args['type_name']) continue;
$found = true;
foreach ($attributes as $name => $value) {
$tags->set_attribute($name, $value);
}
}
if ($found) break;
}
return $tags->get_updated_html();
}
public static function add_dynamic_floating_attributes(array $args = []):void {
if (wp_script_is(self::DYNAMIC_FLOATING_HANDLE)) return;
$args = self::parse_button_floating_args($args);
wp_enqueue_script(
self::DYNAMIC_FLOATING_HANDLE,
Plugin::get_url('assets/floating.js'),
[
self::FLOATING_HANDLE
],
args: [
'in_footer' => true
]
);
wp_localize_script(
self::DYNAMIC_FLOATING_HANDLE,
'CAP_FLOATING_DYNAMIC',
[
'selector' => self::get_button_floating_selector($args),
'attributes' => self::get_button_floating_attributes($args),
]
);
}
public static function get_token():string {
return isset($_POST[self::TOKEN_NAME]) ? (string) $_POST[self::TOKEN_NAME] : '';
}
+13 -3
View File
@@ -4,7 +4,7 @@
* @author cleverogre
* @copyright 2026 CleverOgre
* @license GLP-3.0-or-later
* @version 1.1.2
* @version 1.2.1
* @since 1.1.0
*/
@@ -18,6 +18,7 @@ use Ogre\Captcha\Widget;
use GFForms;
use GFAddOn;
use GFFormDisplay;
use GFCommon;
defined('ABSPATH') || exit;
@@ -115,7 +116,7 @@ final class Addon extends GFAddOn {
}
public function is_enabled(array|null $form = null): bool {
$plugin_enabled = (bool) $this->get_plugin_setting('enabled');
$plugin_enabled = $this->get_plugin_setting('enabled') === '1';
if (is_null($form) || !$plugin_enabled) return $plugin_enabled;
$form_settings = $this->get_form_settings($form);
@@ -123,13 +124,22 @@ final class Addon extends GFAddOn {
return !$form_disabled;
}
public function set_enabled(bool $value): bool {
$settings = (array) $this->get_plugin_settings();
$settings['enabled'] = !!$value ? '1' : '0';
$this->update_plugin_settings($settings);
return true;
}
public function submit_button(string $button_input, array $form): string {
if (!Settings::is_connected() || !$this->is_enabled($form)) return $button_input;
if ((is_admin() && !defined('DOING_AJAX')) || GFCommon::is_form_editor()) return $button_input;
ob_start();
Widget::render();
return ob_get_clean() . $button_input;
$button_input = ob_get_clean() . $button_input;
if (Settings::is_widget_floating()) $button_input = Widget::set_button_floating($button_input);
return $button_input;
}
public function validation(array $validation_result, string $context): array {
+10 -1
View File
@@ -4,7 +4,7 @@
* @author cleverogre
* @copyright 2026 CleverOgre
* @license GLP-3.0-or-later
* @version 1.0.0
* @version 1.2.0
* @since 1.0.0
*/
@@ -13,6 +13,7 @@ namespace Ogre\Captcha\Location;
use Override;
use Ogre\Captcha\Location;
use Ogre\Captcha\Settings;
use Ogre\Captcha\Widget;
use WP_Error;
defined('ABSPATH') || exit;
@@ -27,8 +28,16 @@ final class CommentForm extends Location {
public function activate(): void
{
add_action('comment_form_after_fields', [$this, 'render'], 1);
add_filter('comment_form_submit_button', [$this, 'submit_button'], 10, 2);
add_filter('preprocess_comment', [$this, 'process_form']);
}
public function submit_button(string $submit_button, array $args):string {
if (Settings::is_widget_floating()) {
$submit_button = Widget::set_button_floating($submit_button);
}
return $submit_button;
}
public function process_form(array $commentdata):array {
// Don't process CAPTCHA for comment replies inside admin menu
+7 -1
View File
@@ -4,7 +4,7 @@
* @author cleverogre
* @copyright 2026 CleverOgre
* @license GLP-3.0-or-later
* @version 1.0.0
* @version 1.2.0
* @since 1.0.0
*/
@@ -13,6 +13,7 @@ namespace Ogre\Captcha\Location;
use Override;
use Ogre\Captcha\Location;
use Ogre\Captcha\Settings;
use Ogre\Captcha\Widget;
defined('ABSPATH') || exit;
@@ -39,6 +40,11 @@ final class LoginForm extends Location {
ob_start();
$this->render();
$content .= ob_get_clean();
if (Settings::is_widget_floating()) {
Widget::add_dynamic_floating_attributes();
}
return $content;
}
+5 -4
View File
@@ -4,7 +4,7 @@
* @author cleverogre
* @copyright 2026 CleverOgre
* @license GLP-3.0-or-later
* @version 1.0.0
* @version 1.2.0
* @since 1.0.0
*/
@@ -12,17 +12,17 @@ namespace Ogre\Captcha\Location;
use Ogre\Captcha\API;
use Override;
use Ogre\Captcha\Location;
use Ogre\Captcha\LoginLocation;
use Ogre\Captcha\Settings;
use WP_Error;
use WP_User;
defined('ABSPATH') || exit;
final class LoginPage extends Location {
final class LoginPage extends LoginLocation {
protected function __construct() {
parent::__construct(Settings::LOCATION_LOGIN_PAGE);
parent::__construct(Settings::LOCATION_LOGIN_PAGE, 'login');
}
#[Override]
@@ -34,6 +34,7 @@ final class LoginPage extends Location {
#[Override]
public function activate(): void
{
parent::activate();
add_action('login_form', [$this, 'render']);
add_filter('shake_error_codes', [$this, 'shake_error_codes']);
add_filter('authenticate', [$this, 'process_form'], 20, 1);
+5 -4
View File
@@ -4,24 +4,24 @@
* @author cleverogre
* @copyright 2026 CleverOgre
* @license GLP-3.0-or-later
* @version 1.0.0
* @version 1.2.0
* @since 1.0.0
*/
namespace Ogre\Captcha\Location;
use Override;
use Ogre\Captcha\Location;
use Ogre\Captcha\LoginLocation;
use Ogre\Captcha\Settings;
use WP_Error;
use WP_User;
defined('ABSPATH') || exit;
final class LostPasswordForm extends Location {
final class LostPasswordForm extends LoginLocation {
protected function __construct() {
parent::__construct(Settings::LOCATION_LOST_PASSWORD_FORM);
parent::__construct(Settings::LOCATION_LOST_PASSWORD_FORM, 'lostpassword');
}
#[Override]
@@ -33,6 +33,7 @@ final class LostPasswordForm extends Location {
#[Override]
public function activate(): void
{
parent::activate();
add_action('lostpassword_form', [$this, 'render']);
add_action('lostpassword_post', [$this, 'process_form']);
}
+9 -3
View File
@@ -4,7 +4,7 @@
* @author cleverogre
* @copyright 2026 CleverOgre
* @license GLP-3.0-or-later
* @version 1.0.0
* @version 1.2.0
* @since 1.0.0
*/
@@ -13,6 +13,7 @@ namespace Ogre\Captcha\Location;
use Override;
use Ogre\Captcha\Location;
use Ogre\Captcha\Settings;
use Ogre\Captcha\Widget;
use WP_Error;
use WP_Post;
@@ -27,14 +28,19 @@ final class PostPasswordForm extends Location {
#[Override]
public function activate(): void
{
add_filter('the_password_form', [$this, 'render_form']);
add_filter('the_password_form', [$this, 'render_form'], 10, 3);
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();
$output = str_replace('</form>', ob_get_clean() . '</form>', $output);
if (Settings::is_widget_floating()) {
$output = Widget::set_button_floating($output);
}
return $output;
}
+4 -4
View File
@@ -4,23 +4,23 @@
* @author cleverogre
* @copyright 2026 CleverOgre
* @license GLP-3.0-or-later
* @version 1.0.0
* @version 1.2.0
* @since 1.0.0
*/
namespace Ogre\Captcha\Location;
use Override;
use Ogre\Captcha\Location;
use Ogre\Captcha\LoginLocation;
use Ogre\Captcha\Settings;
use WP_Error;
defined('ABSPATH') || exit;
final class RegistrationPage extends Location {
final class RegistrationPage extends LoginLocation {
protected function __construct() {
parent::__construct(Settings::LOCATION_REGISTRATION_PAGE);
parent::__construct(Settings::LOCATION_REGISTRATION_PAGE, 'register');
}
#[Override]
+10 -1
View File
@@ -4,7 +4,7 @@
* @author cleverogre
* @copyright 2026 CleverOgre
* @license GLP-3.0-or-later
* @version 1.0.0
* @version 1.2.0
* @since 1.0.0
*/
@@ -13,6 +13,7 @@ namespace Ogre\Captcha\Location\WooCommerce;
use Override;
use Ogre\Captcha\Location;
use Ogre\Captcha\Settings;
use Ogre\Captcha\Widget;
use WP_Error;
defined('ABSPATH') || exit;
@@ -33,9 +34,17 @@ final class Checkout extends Location {
public function activate(): void
{
add_action('woocommerce_after_checkout_billing_form', [$this, 'render']);
add_filter('woocommerce_order_button_html', [$this, 'submit_button'], 10, 1);
add_action('woocommerce_after_checkout_validation', [$this, 'process_form'], 10, 2);
}
public function submit_button(string $html):string {
if (Settings::is_widget_floating()) {
$html = Widget::set_button_floating($html);
}
return $html;
}
public function process_form(array $data, WP_Error $errors) {
$this->process($errors);
}
+3 -1
View File
@@ -4,7 +4,7 @@
* @author cleverogre
* @copyright 2026 CleverOgre
* @license GLP-3.0-or-later
* @version 1.0.0
* @version 1.2.0
* @since 1.0.0
*/
@@ -32,7 +32,9 @@ final class Login extends Location {
#[Override]
public function activate(): void
{
add_action('woocommerce_login_form_start', [$this, 'header']);
add_action('woocommerce_login_form', [$this, 'render']);
add_action('woocommerce_login_form_end', [$this, 'footer']);
if (isset($_POST['woocommerce-login-nonce'])) {
add_filter('woocommerce_process_login_errors', [$this, 'process_form'], 10, 3);
}
@@ -4,7 +4,7 @@
* @author cleverogre
* @copyright 2026 CleverOgre
* @license GLP-3.0-or-later
* @version 1.0.0
* @version 1.2.0
* @since 1.0.0
*/
@@ -33,7 +33,9 @@ final class LostPassword extends Location {
#[Override]
public function activate(): void
{
add_action('woocommerce_before_lost_password_form', [$this, 'header']);
add_action('woocommerce_lostpassword_form', [$this, 'render']);
add_action('woocommerce_after_lost_password_form', [$this, 'footer']);
if (isset($_POST['woocommerce-lost-password-nonce'])) {
add_action('lostpassword_post', [$this, 'process_form']);
}
+3 -1
View File
@@ -4,7 +4,7 @@
* @author cleverogre
* @copyright 2026 CleverOgre
* @license GLP-3.0-or-later
* @version 1.0.0
* @version 1.2.0
* @since 1.0.0
*/
@@ -32,7 +32,9 @@ final class Register extends Location {
#[Override]
public function activate(): void
{
add_action('woocommerce_register_form_start', [$this, 'header']);
add_action('woocommerce_register_form', [$this, 'render']);
add_action('woocommerce_register_form_end', [$this, 'footer']);
if (isset($_POST['woocommerce-register-nonce'])) {
add_filter('woocommerce_process_registration_errors', [$this, 'process_form'], 10, 4);
}
+6 -2
View File
@@ -6,14 +6,14 @@
* @author cleverogre
* @copyright 2026 CleverOgre
* @license GLP-3.0-or-later
* @version 1.1.2
* @version 1.2.1
* @since 1.0.0
*
* @wordpress-plugin
* Plugin Name: Ogre CAPTCHA
* Plugin URI: https://git.cleverogre.com/cleverogre/ogre-captcha/
* Description: Protect your site with CAPTCHA
* Version: 1.1.2
* Version: 1.2.1
* Requires at least: 6.0
* Requires PHP: 8.2
* Author: CleverOgre
@@ -66,6 +66,10 @@ final class Captcha extends Plugin {
'inc/location/class-post-password-form.php',
'inc/location/class-registration-page.php',
]);
if (class_exists('WP_CLI')) {
$this->add_file('inc/class-cli.php');
}
}
#[Override]
+1 -1
View File
@@ -1,7 +1,7 @@
{
"$schema": "https://www.schemastore.org/package.json",
"name": "cleverogre/ogre-captcha",
"version": "1.1.2",
"version": "1.2.1",
"title": "Ogre CAPTCHA",
"description": "Protect your site with CAPTCHA",
"author": "CleverOgre",
+19 -1
View File
@@ -3,7 +3,7 @@ Contributors: ogrecooper, cleverogre
Tested up to: 7.0
Requires at least: 6.0
Requires PHP: 8.2
Version: 1.0.1
Version: 1.2.1
License: GPLv3 or later
License URI: https://www.gnu.org/licenses/gpl-3.0.html
Copyright: CleverOgre
@@ -28,6 +28,24 @@ If you don't know what plugin you have downloaded, please contact [CleverOgre](t
== Changelog ==
= 1.2.1 - 2026-06-09 =
* Added WP CLI commands to modify settings
= 1.2.0 - 2026-06-09 =
* Add floating widget mode support
* Add simple select all toggle for form locations settings
= 1.1.2 - 2026-06-08 =
* Prevent captcha in Gravity Forms editor
* Add default widget styling
* Fix validation on multi-page forms
= 1.1.1 - 2026-06-08 =
* Fixed Gravity Forms addon initialization
= 1.1.0 - 2026-06-05 =
* Added Gravity Forms support
= 1.0.1 - 2026-06-05 =
* Update to use UpdatePulse server