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