From 401676e7f36f31a5e5da2bf6caf86a187300d056 Mon Sep 17 00:00:00 2001 From: Cooper Dalrymple Date: Tue, 9 Jun 2026 12:19:49 -0500 Subject: [PATCH 1/8] Fix changelog --- readme.txt | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/readme.txt b/readme.txt index 49f3252..f692d48 100644 --- a/readme.txt +++ b/readme.txt @@ -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.1.2 License: GPLv3 or later License URI: https://www.gnu.org/licenses/gpl-3.0.html Copyright: CleverOgre @@ -28,6 +28,17 @@ If you don't know what plugin you have downloaded, please contact [CleverOgre](t == Changelog == += 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 From 6a6e8f39605b5bf9c2b56f1640b6079db38e8095 Mon Sep 17 00:00:00 2001 From: Cooper Dalrymple Date: Tue, 9 Jun 2026 12:53:19 -0500 Subject: [PATCH 2/8] Add floating mode support --- assets/floating.js | 21 +++ inc/abstract-location.php | 54 +++++- inc/class-settings.php | 63 ++++++- inc/class-widget.php | 164 +++++++++++++++++- inc/gravityforms/class-addon.php | 4 +- inc/location/class-comment-form.php | 9 + inc/location/class-login-form.php | 6 + inc/location/class-login-page.php | 8 +- inc/location/class-lost-password-form.php | 7 +- inc/location/class-post-password-form.php | 6 + inc/location/class-registration-page.php | 6 +- inc/location/woocommerce/class-checkout.php | 9 + inc/location/woocommerce/class-login.php | 2 + .../woocommerce/class-lost-password.php | 2 + inc/location/woocommerce/class-register.php | 2 + 15 files changed, 341 insertions(+), 22 deletions(-) create mode 100644 assets/floating.js diff --git a/assets/floating.js b/assets/floating.js new file mode 100644 index 0000000..72371cc --- /dev/null +++ b/assets/floating.js @@ -0,0 +1,21 @@ +/** + * @package ogre-captcha + * @author cleverogre + * @copyright 2026 CleverOgre + * @license GLP-3.0-or-later + * @version 1.1.2 + * @since 1.1.2 + */ + +(() => { + 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); + } + }); +})(); diff --git a/inc/abstract-location.php b/inc/abstract-location.php index 4a2eded..8fe5dde 100644 --- a/inc/abstract-location.php +++ b/inc/abstract-location.php @@ -11,8 +11,8 @@ 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,56 @@ 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('lostpassword_form', [$this, 'render']); + add_action('login_head', [$this, 'header']); + add_action('login_footer', [$this, 'footer']); + add_action('lostpassword_post', [$this, 'process_form']); + } + + 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(); + } + } diff --git a/inc/class-settings.php b/inc/class-settings.php index 68e99a7..56a65bf 100644 --- a/inc/class-settings.php +++ b/inc/class-settings.php @@ -33,6 +33,13 @@ final class Settings { public const LOCATION_WC_REGISTER = 'wc_register'; public const LOCATION_WC_CHECKOUT = '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']); add_action('admin_init', [$this, 'init']); @@ -44,6 +51,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 +65,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)); } @@ -139,6 +151,16 @@ final class Settings { return self::is_form_location_enabled(self::LOCATION_WC_CHECKOUT); } + public static function get_widget_type():string { + $value = (array) self::get('widget_type'); + $value = !empty($value) ? array_values($value)[0] : ''; + 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 function menu() { if (!current_user_can(self::CAPABILITY)) return; add_options_page( @@ -204,11 +226,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.'), ] ); @@ -250,6 +272,36 @@ final class Settings { ] ); } + + 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? View the demo to try them out.'), + ] + ); + + } public function page() { @@ -334,11 +386,12 @@ final class Settings { echo implode('
', array_map( fn (string $name, string $label):string => sprintf( - '', + '', + esc_attr($args['type'] ?? 'checkbox'), esc_attr(Plugin::get_id()), esc_attr($args['name']), 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']), diff --git a/inc/class-widget.php b/inc/class-widget.php index a0cc760..e4dd8e2 100644 --- a/inc/class-widget.php +++ b/inc/class-widget.php @@ -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,177 @@ 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() + ); + } + + if ($floating && !wp_script_is(self::FLOATING_HANDLE)) { + wp_enqueue_script( + self::FLOATING_HANDLE, + self::get_floating_script_url(), + [ + self::HANDLE + ] + ); } 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( '', - 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); + while (true) { + $found = false; + foreach ($queries as $query) { + if ($tags->next_tag($query)) { + $found = true; + break; + } + } + if (!$found) break; + + if (!is_null($args['type_name']) && !empty($args['type_name']) && $tags->get_attribute('type') !== $args['type_name']) continue; + + foreach ($attributes as $name => $value) { + $tags->set_attribute($name, $value); + } + } + 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 + ] + ); + + 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] : ''; } diff --git a/inc/gravityforms/class-addon.php b/inc/gravityforms/class-addon.php index 161ea35..9554436 100644 --- a/inc/gravityforms/class-addon.php +++ b/inc/gravityforms/class-addon.php @@ -130,7 +130,9 @@ final class Addon extends GFAddOn { 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 { diff --git a/inc/location/class-comment-form.php b/inc/location/class-comment-form.php index db6a7c2..cf507e9 100644 --- a/inc/location/class-comment-form.php +++ b/inc/location/class-comment-form.php @@ -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 diff --git a/inc/location/class-login-form.php b/inc/location/class-login-form.php index 16f8eaa..7d7d57e 100644 --- a/inc/location/class-login-form.php +++ b/inc/location/class-login-form.php @@ -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; } diff --git a/inc/location/class-login-page.php b/inc/location/class-login-page.php index 6be5c9f..147f3d5 100644 --- a/inc/location/class-login-page.php +++ b/inc/location/class-login-page.php @@ -12,17 +12,18 @@ namespace Ogre\Captcha\Location; use Ogre\Captcha\API; use Override; -use Ogre\Captcha\Location; +use Ogre\Captcha\LoginLocation; use Ogre\Captcha\Settings; +use Ogre\Captcha\Widget; 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 +35,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); diff --git a/inc/location/class-lost-password-form.php b/inc/location/class-lost-password-form.php index 74ae73d..70f082a 100644 --- a/inc/location/class-lost-password-form.php +++ b/inc/location/class-lost-password-form.php @@ -11,17 +11,17 @@ 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']); } diff --git a/inc/location/class-post-password-form.php b/inc/location/class-post-password-form.php index a959b64..bbd2691 100644 --- a/inc/location/class-post-password-form.php +++ b/inc/location/class-post-password-form.php @@ -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; @@ -35,6 +36,11 @@ final class PostPasswordForm extends Location { ob_start(); $this->render(); $output .= ob_get_clean(); + + if (Settings::is_widget_floating()) { + $output = Widget::set_button_floating($output); + } + return $output; } diff --git a/inc/location/class-registration-page.php b/inc/location/class-registration-page.php index 92c1a2b..2f01b5d 100644 --- a/inc/location/class-registration-page.php +++ b/inc/location/class-registration-page.php @@ -11,16 +11,16 @@ 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] diff --git a/inc/location/woocommerce/class-checkout.php b/inc/location/woocommerce/class-checkout.php index a3d50f2..ae6ed2e 100644 --- a/inc/location/woocommerce/class-checkout.php +++ b/inc/location/woocommerce/class-checkout.php @@ -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); } diff --git a/inc/location/woocommerce/class-login.php b/inc/location/woocommerce/class-login.php index 66fa069..46b0c5c 100644 --- a/inc/location/woocommerce/class-login.php +++ b/inc/location/woocommerce/class-login.php @@ -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); } diff --git a/inc/location/woocommerce/class-lost-password.php b/inc/location/woocommerce/class-lost-password.php index e366c4e..52d844f 100644 --- a/inc/location/woocommerce/class-lost-password.php +++ b/inc/location/woocommerce/class-lost-password.php @@ -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']); } diff --git a/inc/location/woocommerce/class-register.php b/inc/location/woocommerce/class-register.php index b42cedd..3b6f89c 100644 --- a/inc/location/woocommerce/class-register.php +++ b/inc/location/woocommerce/class-register.php @@ -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); } From 79f95fa0df6948a24cad6706024dd8c059c08443 Mon Sep 17 00:00:00 2001 From: Cooper Dalrymple Date: Tue, 9 Jun 2026 12:56:06 -0500 Subject: [PATCH 3/8] Update version to 1.2.0 --- composer.json | 2 +- inc/abstract-location.php | 2 +- inc/class-settings.php | 2 +- inc/class-widget.php | 2 +- inc/gravityforms/class-addon.php | 2 +- inc/location/class-comment-form.php | 2 +- inc/location/class-login-form.php | 2 +- inc/location/class-login-page.php | 3 +-- inc/location/class-lost-password-form.php | 2 +- inc/location/class-post-password-form.php | 2 +- inc/location/class-registration-page.php | 2 +- inc/location/woocommerce/class-checkout.php | 2 +- inc/location/woocommerce/class-login.php | 2 +- inc/location/woocommerce/class-lost-password.php | 2 +- inc/location/woocommerce/class-register.php | 2 +- ogre-captcha.php | 4 ++-- package.json | 2 +- readme.txt | 5 ++++- 18 files changed, 22 insertions(+), 20 deletions(-) diff --git a/composer.json b/composer.json index ac01a16..07ffd85 100644 --- a/composer.json +++ b/composer.json @@ -1,7 +1,7 @@ { "$schema": "https://getcomposer.org/schema.json", "name": "cleverogre/ogre-captcha", - "version": "1.1.2", + "version": "1.2.0", "title": "Ogre CAPTCHA", "description": "Protect your site with CAPTCHA", "author": "CleverOgre", diff --git a/inc/abstract-location.php b/inc/abstract-location.php index 8fe5dde..0f2513b 100644 --- a/inc/abstract-location.php +++ b/inc/abstract-location.php @@ -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 */ diff --git a/inc/class-settings.php b/inc/class-settings.php index 56a65bf..af615a6 100644 --- a/inc/class-settings.php +++ b/inc/class-settings.php @@ -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 */ diff --git a/inc/class-widget.php b/inc/class-widget.php index e4dd8e2..763f472 100644 --- a/inc/class-widget.php +++ b/inc/class-widget.php @@ -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 */ diff --git a/inc/gravityforms/class-addon.php b/inc/gravityforms/class-addon.php index 9554436..8d94d81 100644 --- a/inc/gravityforms/class-addon.php +++ b/inc/gravityforms/class-addon.php @@ -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.1.0 */ diff --git a/inc/location/class-comment-form.php b/inc/location/class-comment-form.php index cf507e9..f8f1bb9 100644 --- a/inc/location/class-comment-form.php +++ b/inc/location/class-comment-form.php @@ -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 */ diff --git a/inc/location/class-login-form.php b/inc/location/class-login-form.php index 7d7d57e..5160010 100644 --- a/inc/location/class-login-form.php +++ b/inc/location/class-login-form.php @@ -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 */ diff --git a/inc/location/class-login-page.php b/inc/location/class-login-page.php index 147f3d5..fbce3d0 100644 --- a/inc/location/class-login-page.php +++ b/inc/location/class-login-page.php @@ -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 */ @@ -14,7 +14,6 @@ use Ogre\Captcha\API; use Override; use Ogre\Captcha\LoginLocation; use Ogre\Captcha\Settings; -use Ogre\Captcha\Widget; use WP_Error; use WP_User; diff --git a/inc/location/class-lost-password-form.php b/inc/location/class-lost-password-form.php index 70f082a..bdebeb8 100644 --- a/inc/location/class-lost-password-form.php +++ b/inc/location/class-lost-password-form.php @@ -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 */ diff --git a/inc/location/class-post-password-form.php b/inc/location/class-post-password-form.php index bbd2691..5fa5c13 100644 --- a/inc/location/class-post-password-form.php +++ b/inc/location/class-post-password-form.php @@ -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 */ diff --git a/inc/location/class-registration-page.php b/inc/location/class-registration-page.php index 2f01b5d..4a1eb99 100644 --- a/inc/location/class-registration-page.php +++ b/inc/location/class-registration-page.php @@ -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 */ diff --git a/inc/location/woocommerce/class-checkout.php b/inc/location/woocommerce/class-checkout.php index ae6ed2e..0131474 100644 --- a/inc/location/woocommerce/class-checkout.php +++ b/inc/location/woocommerce/class-checkout.php @@ -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 */ diff --git a/inc/location/woocommerce/class-login.php b/inc/location/woocommerce/class-login.php index 46b0c5c..f0f8932 100644 --- a/inc/location/woocommerce/class-login.php +++ b/inc/location/woocommerce/class-login.php @@ -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 */ diff --git a/inc/location/woocommerce/class-lost-password.php b/inc/location/woocommerce/class-lost-password.php index 52d844f..e7d1dbb 100644 --- a/inc/location/woocommerce/class-lost-password.php +++ b/inc/location/woocommerce/class-lost-password.php @@ -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 */ diff --git a/inc/location/woocommerce/class-register.php b/inc/location/woocommerce/class-register.php index 3b6f89c..e7e9b9b 100644 --- a/inc/location/woocommerce/class-register.php +++ b/inc/location/woocommerce/class-register.php @@ -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 */ diff --git a/ogre-captcha.php b/ogre-captcha.php index 7c27d9c..483c417 100644 --- a/ogre-captcha.php +++ b/ogre-captcha.php @@ -6,14 +6,14 @@ * @author cleverogre * @copyright 2026 CleverOgre * @license GLP-3.0-or-later - * @version 1.1.2 + * @version 1.2.0 * @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.0 * Requires at least: 6.0 * Requires PHP: 8.2 * Author: CleverOgre diff --git a/package.json b/package.json index 318428c..35aa75e 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "$schema": "https://www.schemastore.org/package.json", "name": "cleverogre/ogre-captcha", - "version": "1.1.2", + "version": "1.2.0", "title": "Ogre CAPTCHA", "description": "Protect your site with CAPTCHA", "author": "CleverOgre", diff --git a/readme.txt b/readme.txt index f692d48..2c3cf80 100644 --- a/readme.txt +++ b/readme.txt @@ -3,7 +3,7 @@ Contributors: ogrecooper, cleverogre Tested up to: 7.0 Requires at least: 6.0 Requires PHP: 8.2 -Version: 1.1.2 +Version: 1.2.0 License: GPLv3 or later License URI: https://www.gnu.org/licenses/gpl-3.0.html Copyright: CleverOgre @@ -28,6 +28,9 @@ If you don't know what plugin you have downloaded, please contact [CleverOgre](t == Changelog == += 1.2.0 - 2026-06-09 = +* Add floating widget mode support + = 1.1.2 - 2026-06-08 = * Prevent captcha in Gravity Forms editor * Add default widget styling From 8159c4a46c60576cbf38ff5c2f5ae8afb158b0d1 Mon Sep 17 00:00:00 2001 From: Cooper Dalrymple Date: Tue, 9 Jun 2026 13:04:40 -0500 Subject: [PATCH 4/8] Add simple select all option to forms settings --- assets/floating.js | 4 ++-- inc/class-settings.php | 11 +++++++++++ readme.txt | 1 + 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/assets/floating.js b/assets/floating.js index 72371cc..eb598a6 100644 --- a/assets/floating.js +++ b/assets/floating.js @@ -3,8 +3,8 @@ * @author cleverogre * @copyright 2026 CleverOgre * @license GLP-3.0-or-later - * @version 1.1.2 - * @since 1.1.2 + * @version 1.2.0 + * @since 1.2.0 */ (() => { diff --git a/inc/class-settings.php b/inc/class-settings.php index af615a6..a4f50be 100644 --- a/inc/class-settings.php +++ b/inc/class-settings.php @@ -250,6 +250,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.'), ] ); @@ -269,6 +270,7 @@ final class Settings { self::LOCATION_WC_REGISTER => Plugin::__('Register form'), self::LOCATION_WC_CHECKOUT => Plugin::__('Checkout form'), ], + 'select_all' => true, ] ); } @@ -384,6 +386,15 @@ final class Settings { public function checkbox_field(array $args):void { if (empty($args['name'] ?? '') || empty($args['options'] ?? [])) return; + if (!!($args['select_all'] ?? false) && ($args['type'] ?? 'checkbox') == 'checkbox') { + printf( + '
', + esc_attr(Plugin::get_id()), + esc_attr($args['name']), + Plugin::esc_html__('Select All') + ); + } + echo implode('
', array_map( fn (string $name, string $label):string => sprintf( '', diff --git a/readme.txt b/readme.txt index 2c3cf80..a7269f7 100644 --- a/readme.txt +++ b/readme.txt @@ -30,6 +30,7 @@ If you don't know what plugin you have downloaded, please contact [CleverOgre](t = 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 From 6ced723e4f15cf3c4ddee30656932a558e274c41 Mon Sep 17 00:00:00 2001 From: Cooper Dalrymple Date: Tue, 9 Jun 2026 13:09:33 -0500 Subject: [PATCH 5/8] Fix lost password form --- inc/abstract-location.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/inc/abstract-location.php b/inc/abstract-location.php index 0f2513b..9c657bf 100644 --- a/inc/abstract-location.php +++ b/inc/abstract-location.php @@ -96,10 +96,8 @@ abstract class LoginLocation extends Location { #[Override] public function activate(): void { - add_action('lostpassword_form', [$this, 'render']); add_action('login_head', [$this, 'header']); add_action('login_footer', [$this, 'footer']); - add_action('lostpassword_post', [$this, 'process_form']); } private function get_action():string { From cc135e0401931bdc29aca7849a43b31256c6cd8d Mon Sep 17 00:00:00 2001 From: Cooper Dalrymple Date: Tue, 9 Jun 2026 13:19:51 -0500 Subject: [PATCH 6/8] Fix floating widget in post password form --- inc/class-widget.php | 11 ++++++++++- inc/location/class-post-password-form.php | 4 ++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/inc/class-widget.php b/inc/class-widget.php index 763f472..be8a169 100644 --- a/inc/class-widget.php +++ b/inc/class-widget.php @@ -39,7 +39,10 @@ final class Widget { if (!wp_script_is(self::HANDLE)) { wp_enqueue_script( self::HANDLE, - self::get_script_url() + self::get_script_url(), + args: [ + 'in_footer' => true + ] ); } @@ -49,6 +52,9 @@ final class Widget { self::get_floating_script_url(), [ self::HANDLE + ], + args: [ + 'in_footer' => true ] ); } @@ -186,6 +192,9 @@ final class Widget { Plugin::get_url('assets/floating.js'), [ self::FLOATING_HANDLE + ], + args: [ + 'in_footer' => true ] ); diff --git a/inc/location/class-post-password-form.php b/inc/location/class-post-password-form.php index 5fa5c13..c846c30 100644 --- a/inc/location/class-post-password-form.php +++ b/inc/location/class-post-password-form.php @@ -28,14 +28,14 @@ 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('', ob_get_clean() . '', $output); if (Settings::is_widget_floating()) { $output = Widget::set_button_floating($output); From 2826db7951f82c7e77ee6573b4c0897035b48173 Mon Sep 17 00:00:00 2001 From: Cooper Dalrymple Date: Tue, 9 Jun 2026 13:27:41 -0500 Subject: [PATCH 7/8] Fix dynamic floating widget --- assets/floating.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/assets/floating.js b/assets/floating.js index eb598a6..9c1d819 100644 --- a/assets/floating.js +++ b/assets/floating.js @@ -17,5 +17,10 @@ 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); }); })(); From 3bcedb614bcf815e9874a7387074c6f35247f7ba Mon Sep 17 00:00:00 2001 From: Cooper Dalrymple Date: Tue, 9 Jun 2026 13:39:43 -0500 Subject: [PATCH 8/8] Fix floating target `button` tag --- inc/class-widget.php | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/inc/class-widget.php b/inc/class-widget.php index be8a169..153bddc 100644 --- a/inc/class-widget.php +++ b/inc/class-widget.php @@ -163,21 +163,19 @@ final class Widget { } $tags = new WP_HTML_Tag_Processor($html); - while (true) { + $tags->next_tag(); + $tags->set_bookmark('top'); + foreach ($queries as $query) { + $tags->seek('top'); $found = false; - foreach ($queries as $query) { - if ($tags->next_tag($query)) { - $found = true; - break; + 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; - - if (!is_null($args['type_name']) && !empty($args['type_name']) && $tags->get_attribute('type') !== $args['type_name']) continue; - - foreach ($attributes as $name => $value) { - $tags->set_attribute($name, $value); - } + if ($found) break; } return $tags->get_updated_html(); }