diff --git a/assets/floating.js b/assets/floating.js
new file mode 100644
index 0000000..9c1d819
--- /dev/null
+++ b/assets/floating.js
@@ -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);
+ });
+})();
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 4a2eded..9c657bf 100644
--- a/inc/abstract-location.php
+++ b/inc/abstract-location.php
@@ -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();
+ }
+
}
diff --git a/inc/class-settings.php b/inc/class-settings.php
index 68e99a7..a4f50be 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
*/
@@ -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.'),
]
);
@@ -228,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.'),
]
);
@@ -247,9 +270,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? View the demo to try them out.'),
+ ]
+ );
+
+
}
public function page() {
@@ -332,13 +386,23 @@ 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(
- '',
+ '',
+ 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..153bddc 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
*/
@@ -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(
'',
- 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] : '';
}
diff --git a/inc/gravityforms/class-addon.php b/inc/gravityforms/class-addon.php
index 161ea35..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
*/
@@ -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..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
*/
@@ -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..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
*/
@@ -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..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
*/
@@ -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);
diff --git a/inc/location/class-lost-password-form.php b/inc/location/class-lost-password-form.php
index 74ae73d..bdebeb8 100644
--- a/inc/location/class-lost-password-form.php
+++ b/inc/location/class-lost-password-form.php
@@ -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']);
}
diff --git a/inc/location/class-post-password-form.php b/inc/location/class-post-password-form.php
index a959b64..c846c30 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
*/
@@ -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('', ob_get_clean() . '', $output);
+
+ 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..4a1eb99 100644
--- a/inc/location/class-registration-page.php
+++ b/inc/location/class-registration-page.php
@@ -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]
diff --git a/inc/location/woocommerce/class-checkout.php b/inc/location/woocommerce/class-checkout.php
index a3d50f2..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
*/
@@ -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..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
*/
@@ -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..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
*/
@@ -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..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
*/
@@ -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);
}
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 49f3252..a7269f7 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.2.0
License: GPLv3 or later
License URI: https://www.gnu.org/licenses/gpl-3.0.html
Copyright: CleverOgre
@@ -28,6 +28,21 @@ 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
+* 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