24 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
Cooper Dalrymple 2e7b565407 Prevent captcha in form editor 2026-06-08 15:45:24 -05:00
Cooper Dalrymple ac7760db55 Update version to 1.1.2 2026-06-08 15:37:27 -05:00
Cooper Dalrymple 26d318160b Add default widget styling 2026-06-08 15:34:53 -05:00
Cooper Dalrymple d72ad2c866 Fix captcha validation on multi-page forms 2026-06-08 15:30:14 -05:00
Cooper Dalrymple defa5fe39d Update version to 1.1.1 2026-06-08 14:26:29 -05:00
Cooper Dalrymple b8e0322245 Fix settings page loading 2026-06-08 14:25:49 -05:00
ogrecooper 962bd4bcc5 Merge pull request 'Added Gravity Forms Addon' (#2) from gravityforms into main
Reviewed-on: #2
2026-06-05 22:44:41 +00:00
Cooper Dalrymple 15a89a140f Added Gravity Forms Addon 2026-06-05 17:44:26 -05:00
Cooper Dalrymple 7da66983a9 Push to version 1.0.1 2026-06-05 15:38:00 -05:00
Cooper Dalrymple ef48fe9c21 Setup license requirement 2026-06-05 15:37:47 -05:00
Cooper Dalrymple 891d0ab3fc Switch to updatepulse 2026-06-05 15:35:20 -05:00
25 changed files with 1144 additions and 162 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);
});
})();
+12
View File
@@ -0,0 +1,12 @@
/**
* @package ogre-captcha
* @author cleverogre
* @copyright 2026 CleverOgre
* @license GLP-3.0-or-later
* @version 1.1.2
* @since 1.1.2
*/
:root :where(cap-widget) {
flex-basis: 100%;
}
+29 -18
View File
@@ -1,7 +1,7 @@
{
"$schema": "https://getcomposer.org/schema.json",
"name": "cleverogre/ogre-captcha",
"version": "1.0.0",
"version": "1.2.1",
"title": "Ogre CAPTCHA",
"description": "Protect your site with CAPTCHA",
"author": "CleverOgre",
@@ -15,21 +15,21 @@
],
"homepage": "https://cleverogre.com",
"repositories": {
"cleverogre/plugin-update-checker": {
"type": "vcs",
"url": "git@git.cleverogre.com:cleverogre/plugin-update-checker.git"
},
"cleverogre/updatepulse-updater": {
"type": "vcs",
"url": "git@git.cleverogre.com:cleverogre/updatepulse-updater.git"
},
"cleverogre/plugin-framework": {
"type": "vcs",
"url": "git@git.cleverogre.com:cleverogre/plugin-framework.git"
},
"wp-package-updater": {
"type": "package",
"package": {
"name": "froger-me/wp-package-updater",
"version": "1.4.0",
"source": {
"url": "https://github.com/froger-me/wp-package-updater.git",
"type": "git",
"reference": "master"
}
}
"advancedcustomfields": {
"type": "composer",
"url": "https://connect.advancedcustomfields.com"
}
},
"require": {
@@ -38,17 +38,28 @@
"replace": {
"wpengine/advanced-custom-fields-pro": "*"
},
"scripts": {
"post-update-cmd": [
"php vendor/magicoli/wp-package-updater-lib/install.php"
]
},
"config": {
"allow-plugins": {
"composer/installers": true
},
"optimize-autoloader": true
},
"extra": {
"installer-paths": {
"acf/": [
"wpengine/advanced-custom-fields-pro"
],
"lib/{$name}/": [
"cleverogre/plugin-update-checker",
"cleverogre/updatepulse-updater"
]
}
},
"autoload": {
"files": [
"ogre-captcha.php"
]
}
},
"minimum-stability": "dev",
"prefer-stable": true
}
Generated
+195 -78
View File
@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "978b7fe126b61796750d67c4c7231417",
"content-hash": "49f5e6f919f33ce6a3edc2992620a64a",
"packages": [
{
"name": "cleverogre/plugin-framework",
@@ -12,13 +12,12 @@
"source": {
"type": "git",
"url": "git@git.cleverogre.com:cleverogre/plugin-framework.git",
"reference": "6ba78008013665151d0ffecb94d732baa522caec"
"reference": "74700a249ae3b5e559c071e2467764da4645a26f"
},
"require": {
"froger-me/wp-package-updater": "^1.4.0",
"magicoli/wp-package-updater-lib": "^0.1.9",
"wpengine/advanced-custom-fields-pro": "^6.4.2",
"yahnis-elsts/plugin-update-checker": "^5.0"
"cleverogre/plugin-update-checker": "dev-main",
"cleverogre/updatepulse-updater": "dev-main",
"wpengine/advanced-custom-fields-pro": "^6.4.2"
},
"default-branch": true,
"type": "library",
@@ -26,6 +25,10 @@
"installer-paths": {
"acf/": [
"wpengine/advanced-custom-fields-pro"
],
"lib/{$name}/": [
"cleverogre/plugin-update-checker",
"cleverogre/updatepulse-updater"
]
}
},
@@ -38,11 +41,6 @@
"inc/abstract-plugin.php"
]
},
"scripts": {
"post-update-cmd": [
"php vendor/magicoli/wp-package-updater-lib/install.php"
]
},
"license": [
"GPL-3.0+"
],
@@ -53,77 +51,26 @@
"Plugin",
"WordPress"
],
"time": "2026-04-28T13:19:19+00:00"
"time": "2026-06-05T20:21:23+00:00"
},
{
"name": "froger-me/wp-package-updater",
"version": "1.4.0",
"name": "cleverogre/plugin-update-checker",
"version": "dev-main",
"source": {
"type": "git",
"url": "https://github.com/froger-me/wp-package-updater.git",
"reference": "master"
},
"type": "library"
},
{
"name": "magicoli/wp-package-updater-lib",
"version": "v0.1.10",
"source": {
"type": "git",
"url": "https://github.com/magicoli/wp-package-updater-lib.git",
"reference": "e822132741c08d054fb97f249b1143c220f30e6f"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/magicoli/wp-package-updater-lib/zipball/e822132741c08d054fb97f249b1143c220f30e6f",
"reference": "e822132741c08d054fb97f249b1143c220f30e6f",
"shasum": ""
},
"require-dev": {
"froger-me/wp-plugin-update-server": "*"
},
"type": "library",
"notification-url": "https://packagist.org/downloads/",
"license": [
"AGPL-3.0-or-later"
],
"authors": [
{
"name": "Magiiic"
}
],
"description": "Composer package for wp-package-updater library.",
"support": {
"issues": "https://github.com/magicoli/wp-package-updater-lib/issues",
"source": "https://github.com/magicoli/wp-package-updater-lib/tree/v0.1.10"
},
"time": "2023-06-09T12:42:28+00:00"
},
{
"name": "yahnis-elsts/plugin-update-checker",
"version": "v5.7",
"source": {
"type": "git",
"url": "https://github.com/YahnisElsts/plugin-update-checker.git",
"reference": "275a96a2a18d03c34c87f35cb68673c8c49ac3b1"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/YahnisElsts/plugin-update-checker/zipball/275a96a2a18d03c34c87f35cb68673c8c49ac3b1",
"reference": "275a96a2a18d03c34c87f35cb68673c8c49ac3b1",
"shasum": ""
"url": "git@git.cleverogre.com:cleverogre/plugin-update-checker.git",
"reference": "b05c3dd4cf01f45544358671018dd5b5851cd079"
},
"require": {
"composer/installers": "~1.0",
"ext-json": "*",
"php": ">=5.6.20"
},
"type": "library",
"autoload": {
"files": [
"load-v5p7.php"
]
"default-branch": true,
"type": "wordpress-plugin",
"extra": {
"installer-name": "plugin-update-checker"
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
@@ -143,20 +90,190 @@
"theme updates",
"wordpress"
],
"support": {
"issues": "https://github.com/YahnisElsts/plugin-update-checker/issues",
"source": "https://github.com/YahnisElsts/plugin-update-checker/tree/v5.7"
"time": "2026-06-05T22:05:11+00:00"
},
{
"name": "cleverogre/updatepulse-updater",
"version": "dev-main",
"source": {
"type": "git",
"url": "git@git.cleverogre.com:cleverogre/updatepulse-updater.git",
"reference": "0e57c729b09f426d26add0d7c872ce60d3c8cacf"
},
"time": "2026-05-26T12:48:45+00:00"
"require": {
"composer/installers": "~1.0"
},
"default-branch": true,
"type": "wordpress-plugin",
"extra": {
"installer-name": "updatepulse-updater"
},
"license": [
"GPL-3.0+"
],
"description": "Used to enable updates for plugins and themes distributed via UpdatePulse Server.",
"homepage": "https://github.com/Anyape/updatepulse-server-integration/tree/main/dummy-plugin/lib/updatepulse-updater",
"time": "2026-06-05T20:17:30+00:00"
},
{
"name": "composer/installers",
"version": "v1.12.0",
"source": {
"type": "git",
"url": "https://github.com/composer/installers.git",
"reference": "d20a64ed3c94748397ff5973488761b22f6d3f19"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/composer/installers/zipball/d20a64ed3c94748397ff5973488761b22f6d3f19",
"reference": "d20a64ed3c94748397ff5973488761b22f6d3f19",
"shasum": ""
},
"require": {
"composer-plugin-api": "^1.0 || ^2.0"
},
"replace": {
"roundcube/plugin-installer": "*",
"shama/baton": "*"
},
"require-dev": {
"composer/composer": "1.6.* || ^2.0",
"composer/semver": "^1 || ^3",
"phpstan/phpstan": "^0.12.55",
"phpstan/phpstan-phpunit": "^0.12.16",
"symfony/phpunit-bridge": "^4.2 || ^5",
"symfony/process": "^2.3"
},
"type": "composer-plugin",
"extra": {
"class": "Composer\\Installers\\Plugin",
"branch-alias": {
"dev-main": "1.x-dev"
}
},
"autoload": {
"psr-4": {
"Composer\\Installers\\": "src/Composer/Installers"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Kyle Robinson Young",
"email": "kyle@dontkry.com",
"homepage": "https://github.com/shama"
}
],
"description": "A multi-framework Composer library installer",
"homepage": "https://composer.github.io/installers/",
"keywords": [
"Craft",
"Dolibarr",
"Eliasis",
"Hurad",
"ImageCMS",
"Kanboard",
"Lan Management System",
"MODX Evo",
"MantisBT",
"Mautic",
"Maya",
"OXID",
"Plentymarkets",
"Porto",
"RadPHP",
"SMF",
"Starbug",
"Thelia",
"Whmcs",
"WolfCMS",
"agl",
"aimeos",
"annotatecms",
"attogram",
"bitrix",
"cakephp",
"chef",
"cockpit",
"codeigniter",
"concrete5",
"croogo",
"dokuwiki",
"drupal",
"eZ Platform",
"elgg",
"expressionengine",
"fuelphp",
"grav",
"installer",
"itop",
"joomla",
"known",
"kohana",
"laravel",
"lavalite",
"lithium",
"magento",
"majima",
"mako",
"mediawiki",
"miaoxing",
"modulework",
"modx",
"moodle",
"osclass",
"pantheon",
"phpbb",
"piwik",
"ppi",
"processwire",
"puppet",
"pxcms",
"reindex",
"roundcube",
"shopware",
"silverstripe",
"sydes",
"sylius",
"symfony",
"tastyigniter",
"typo3",
"wordpress",
"yawik",
"zend",
"zikula"
],
"support": {
"issues": "https://github.com/composer/installers/issues",
"source": "https://github.com/composer/installers/tree/v1.12.0"
},
"funding": [
{
"url": "https://packagist.com",
"type": "custom"
},
{
"url": "https://github.com/composer",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/composer/composer",
"type": "tidelift"
}
],
"time": "2021-09-13T08:19:44+00:00"
}
],
"packages-dev": [],
"aliases": [],
"minimum-stability": "stable",
"minimum-stability": "dev",
"stability-flags": {
"cleverogre/plugin-framework": 20
},
"prefer-stable": false,
"prefer-stable": true,
"prefer-lowest": false,
"platform": {},
"platform-dev": {},
+2
View File
@@ -44,12 +44,14 @@ gulp.task(
gulp.task('package-copy', () => {
return gulp.src([
'assets/**/*',
'inc/**/*',
'lib/**/*',
'vendor/**/*',
'LICENSE',
`${NAME}.php`,
'readme.txt',
'updatepulse.json',
'!vendor/**/node_modules/**/*',
], {
base: './',
+60 -22
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;
@@ -20,9 +20,6 @@ defined('ABSPATH') || exit;
abstract class Location {
use Singleton;
public const ERROR_EMPTY_TOKEN = 'empty_captcha_token';
public const ERROR_INVALID_CAPTCHA = 'invalid_captcha_result';
protected string $name;
protected function __construct(string $name) {
@@ -54,32 +51,73 @@ abstract class Location {
Widget::render();
}
public function process(WP_Error $errors):void {
if (!Settings::is_connected()) return;
$token = Widget::get_token();
if (empty($token)) {
$errors->add(self::ERROR_EMPTY_TOKEN, Plugin::__('<strong>Error:</strong> Please complete CAPTCHA verification.'));
return;
}
if (!API::verify($token)) {
$errors->add(self::ERROR_INVALID_CAPTCHA, Plugin::__('<strong>Error:</strong> Your answer was incorrect - please try again.'));
return;
}
public function process(WP_Error|null $errors = null):void {
API::process(Widget::get_token(), $errors);
}
protected function print_errors(WP_Error $errors):void {
if (!$errors->has_errors()) return;
ob_start();
echo '<ul>';
$messages = [];
foreach ($errors->get_error_codes() as $code) {
foreach ($errors->get_error_messages($code) as $message) {
echo '<li>' . wp_kses($message, ['strong']) . '</li>';
$messages[] = $message;
}
}
echo '</ul>';
ob_start();
echo '<ul><li>' . implode('</li><li>', $messages) . '</li></ul>';
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();
}
}
+18
View File
@@ -10,12 +10,17 @@
namespace Ogre\Captcha;
use Ogre\Captcha as Plugin;
use Ogre\Captcha\Settings;
use WP_Error;
defined('ABSPATH') || exit;
final class API {
public const ERROR_EMPTY_TOKEN = 'empty_captcha_token';
public const ERROR_INVALID_CAPTCHA = 'invalid_captcha_result';
private static function transaction(string $url, array $data):array|string|bool {
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_POSTFIELDS, wp_json_encode($data));
@@ -52,4 +57,17 @@ final class API {
return is_bool($result) ? $result : false;
}
public static function process(string $token, WP_Error|null $errors = null): WP_Error {
if (is_null($errors)) $errors = new WP_Error();
if (!Settings::is_connected()) return $errors;
if (empty($token)) {
$errors->add(self::ERROR_EMPTY_TOKEN, Plugin::__('Please complete CAPTCHA verification.'));
} else if (!API::verify($token)) {
$errors->add(self::ERROR_INVALID_CAPTCHA, Plugin::__('Your answer was incorrect - please try again.'));
}
return $errors;
}
}
+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);
+153 -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,7 +78,22 @@ final class Settings {
return $data;
}
private static function is_checked(string $name, string $value):bool {
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])) {
unset($data[$key]);
} else {
$data[$key] = $value;
}
update_option(Plugin::get_id(), $data);
}
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));
}
@@ -62,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());
}
@@ -89,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);
}
@@ -129,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(
@@ -194,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.'),
]
);
@@ -218,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.'),
]
);
@@ -237,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() {
@@ -322,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']),
+175 -8
View File
@@ -4,19 +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;
use Ogre\Captcha as Plugin;
use Ogre\Captcha\Settings;
use WP_HTML_Tag_Processor;
defined('ABSPATH') || exit;
final class Widget {
private const SCRIPT_HANDLE = 'cap';
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 {
@@ -24,21 +28,184 @@ final class Widget {
return Settings::get_instance_url('assets/widget.js');
}
public static function enqueue_script():void {
if (!Settings::is_connected() || wp_script_is(self::SCRIPT_HANDLE)) return;
wp_enqueue_script(self::SCRIPT_HANDLE, self::get_script_url());
private static function get_floating_script_url():string {
if (!Settings::is_connected()) return '';
return Settings::get_instance_url('assets/floating.js');
}
public static function render(string $id = 'cap'):void {
public static function enqueue_assets(bool $floating = false):void {
if (!Settings::is_connected()) return;
self::enqueue_script();
if (!wp_script_is(self::HANDLE)) {
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')
);
}
}
public static function render(array $args = []):void {
if (!Settings::is_connected()) return;
$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] : '';
}
+161
View File
@@ -0,0 +1,161 @@
<?php
/**
* @package ogre-captcha
* @author cleverogre
* @copyright 2026 CleverOgre
* @license GLP-3.0-or-later
* @version 1.2.1
* @since 1.1.0
*/
namespace Ogre\Captcha\GravityForms;
use Ogre\Singleton;
use Ogre\Captcha as Plugin;
use Ogre\Captcha\API;
use Ogre\Captcha\Settings;
use Ogre\Captcha\Widget;
use GFForms;
use GFAddOn;
use GFFormDisplay;
use GFCommon;
defined('ABSPATH') || exit;
GFForms::include_addon_framework();
final class Addon extends GFAddOn {
use Singleton;
protected $_min_gravityforms_version = '2.6.3.2';
public static function get_instance(): Addon {
return self::instance();
}
function __construct() {
$this->_version = Plugin::get_version();
$this->_slug = Plugin::get_id();
$this->_path = sprintf(
'%1$s/%1$s.php',
Plugin::get_id()
);
$this->_full_path = Plugin::get_file();
$this->_title = sprintf(
Plugin::__('%s Add-On'),
Plugin::get_title()
);
$this->_short_title = Plugin::get_title();
parent::__construct();
}
public function init() {
add_filter('gform_submit_button', [$this, 'submit_button'], 10, 2);
add_filter('gform_validation', [$this, 'validation'], 10, 2);
parent::init();
}
public function get_menu_icon() {
return 'gform-icon--lock';
}
public function plugin_settings_fields() {
return [
[
'title' => esc_html(sprintf(
Plugin::__('%s Settings'),
Plugin::get_title()
)),
'description' => sprintf(
Plugin::__('Looking to configure your CAPTCHA connection? You can find the settings on <a href="%s">this page</a>.'),
esc_url(Settings::get_url())
),
'fields' => [
[
'label' => sprintf(
Plugin::esc_html__('Enable %s'),
Plugin::get_title()
),
'type' => 'checkbox',
'name' => 'enabled',
'choices' => [
[
'label' => Plugin::esc_html__('Enable CAPTCHA on all forms by default.'),
'name' => 'enabled',
]
]
]
]
]
];
}
public function form_settings_fields($form) {
return [
[
'title' => esc_html(sprintf(
Plugin::__('%s Settings'),
Plugin::get_title()
)),
'fields' => [
[
'type' => 'checkbox',
'name' => 'disabled',
'choices' => [
[
'label' => Plugin::esc_html__('Disable CAPTCHA for this form.'),
'name' => 'disabled',
]
]
]
]
]
];
}
public function is_enabled(array|null $form = null): bool {
$plugin_enabled = $this->get_plugin_setting('enabled') === '1';
if (is_null($form) || !$plugin_enabled) return $plugin_enabled;
$form_settings = $this->get_form_settings($form);
$form_disabled = (bool) rgar($form_settings, 'disabled');
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();
$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 {
if ($context != 'form-submit') return $validation_result;
$form = $validation_result['form'];
if (!GFFormDisplay::is_last_page($form) || rgpost('action') == 'heartbeat') return $validation_result;
if (!Settings::is_connected() || !$this->is_enabled($form)) return $validation_result;
$errors = API::process(Widget::get_token());
if ($errors->has_errors()) {
$validation_result['is_valid'] = false;
}
// TODO: Mark as spam (optional)
return $validation_result;
}
}
+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;
}
+8 -6
View File
@@ -4,24 +4,25 @@
* @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 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]
@@ -33,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);
@@ -42,8 +44,8 @@ final class LoginPage extends Location {
return array_merge(
$shake_error_codes,
[
self::ERROR_EMPTY_TOKEN,
self::ERROR_INVALID_CAPTCHA,
API::ERROR_EMPTY_TOKEN,
API::ERROR_INVALID_CAPTCHA,
]
);
}
+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);
}
+31 -3
View File
@@ -6,14 +6,14 @@
* @author cleverogre
* @copyright 2026 CleverOgre
* @license GLP-3.0-or-later
* @version 1.0.0
* @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.0.0
* Version: 1.2.1
* Requires at least: 6.0
* Requires PHP: 8.2
* Author: CleverOgre
@@ -23,11 +23,19 @@
* License URI: http://www.gnu.org/licenses/gpl-3.0.html
* Update URI: https://git.cleverogre.com/cleverogre/ogre-captcha/
* Domain Path: /lang
* Icon1x: https://plugins.cleverogre.com/wp-content/uploads/ogre-captcha-icon-128x128.png
* Icon2x: https://plugins.cleverogre.com/wp-content/uploads/ogre-captcha-icon-256x256.png
* BannerLow: https://plugins.cleverogre.com/wp-content/uploads/ogre-captcha-banner-772x250.png
* BannerHigh: https://plugins.cleverogre.com/wp-content/uploads/ogre-captcha-banner-1544x500.png
* Requires License: yes
*/
namespace Ogre;
use Ogre\Captcha\Settings;
use Ogre\Captcha\GravityForms\Addon;
use GFAddOn;
use Override;
defined('ABSPATH') || exit;
@@ -39,6 +47,10 @@ final class Captcha extends Plugin {
return is_plugin_active('woocommerce/woocommerce.php');
}
public static function is_gravityforms_active():bool {
return is_plugin_active('gravityforms/gravityforms.php');
}
protected function __construct() {
parent::__construct();
@@ -55,17 +67,25 @@ final class Captcha extends Plugin {
'inc/location/class-registration-page.php',
]);
add_action('plugins_loaded', [$this, 'load_woocommerce'], 9, 0);
if (class_exists('WP_CLI')) {
$this->add_file('inc/class-cli.php');
}
}
#[Override]
protected function enable():void {
parent::enable();
add_filter('plugin_action_links_' . self::get_basename(), [$this, 'action_links']);
add_action('plugins_loaded', [$this, 'load_woocommerce'], 2, 0);
add_action('gform_loaded', [$this, 'load_gravityforms'], 5, 0);
}
#[Override]
protected function disable():void {
parent::disable();
remove_filter('plugin_action_links_' . self::get_basename(), [$this, 'action_links']);
remove_action('plugins_loaded', [$this, 'load_woocommerce'], 2);
remove_action('gform_loaded', [$this, 'load_gravityforms'], 5);
}
public function action_links(array $links):array {
@@ -90,6 +110,14 @@ final class Captcha extends Plugin {
]);
}
public function load_gravityforms():void {
if (!self::is_gravityforms_active() || !method_exists('GFForms', 'include_addon_framework')) return;
require_once('inc/gravityforms/class-addon.php');
GFAddOn::register(Addon::class);
}
}
Captcha::instance();
+1 -1
View File
@@ -1,7 +1,7 @@
{
"$schema": "https://www.schemastore.org/package.json",
"name": "cleverogre/ogre-captcha",
"version": "1.0.0",
"version": "1.2.1",
"title": "Ogre CAPTCHA",
"description": "Protect your site with CAPTCHA",
"author": "CleverOgre",
+22 -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.0
Version: 1.2.1
License: GPLv3 or later
License URI: https://www.gnu.org/licenses/gpl-3.0.html
Copyright: CleverOgre
@@ -28,5 +28,26 @@ 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
= 1.0.0 - 2026-06-04 =
* Initial release
+3
View File
@@ -0,0 +1,3 @@
{
"server": "https://plugins.cleverogre.com"
}