Add floating mode support
This commit is contained in:
@@ -0,0 +1,21 @@
|
||||
/**
|
||||
* @package ogre-captcha
|
||||
* @author cleverogre
|
||||
* @copyright 2026 CleverOgre
|
||||
* @license GLP-3.0-or-later
|
||||
* @version 1.1.2
|
||||
* @since 1.1.2
|
||||
*/
|
||||
|
||||
(() => {
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
if (typeof window['CAP_FLOATING_DYNAMIC'] === 'undefined') return;
|
||||
|
||||
const element = document.body.querySelector(CAP_FLOATING_DYNAMIC['selector']);
|
||||
if (element === null) return;
|
||||
|
||||
for (const [name, value] of Object.entries(CAP_FLOATING_DYNAMIC['attributes'])) {
|
||||
element.setAttribute(name, value);
|
||||
}
|
||||
});
|
||||
})();
|
||||
@@ -11,8 +11,8 @@
|
||||
namespace Ogre\Captcha;
|
||||
|
||||
use Ogre\Singleton;
|
||||
use Ogre\Captcha as Plugin;
|
||||
use Ogre\Captcha\Settings;
|
||||
use Override;
|
||||
use WP_Error;
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
@@ -70,4 +70,56 @@ abstract class Location {
|
||||
wp_die(ob_get_clean());
|
||||
}
|
||||
|
||||
public function header():void {
|
||||
if (!Settings::is_widget_floating()) return;
|
||||
ob_start();
|
||||
}
|
||||
|
||||
public function footer():void {
|
||||
if (!Settings::is_widget_floating()) return;
|
||||
$html = ob_get_clean();
|
||||
$html = Widget::set_button_floating($html);
|
||||
echo $html;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
abstract class LoginLocation extends Location {
|
||||
|
||||
protected string $action;
|
||||
|
||||
protected function __construct(string $name, string $action) {
|
||||
$this->action = $action;
|
||||
parent::__construct($name);
|
||||
}
|
||||
|
||||
#[Override]
|
||||
public function activate(): void
|
||||
{
|
||||
add_action('lostpassword_form', [$this, 'render']);
|
||||
add_action('login_head', [$this, 'header']);
|
||||
add_action('login_footer', [$this, 'footer']);
|
||||
add_action('lostpassword_post', [$this, 'process_form']);
|
||||
}
|
||||
|
||||
private function get_action():string {
|
||||
return isset($_REQUEST['action']) && is_string($_REQUEST['action']) ? $_REQUEST['action'] : 'login';
|
||||
}
|
||||
|
||||
protected function is_action():bool {
|
||||
return $this->get_action() == $this->action;
|
||||
}
|
||||
|
||||
#[Override]
|
||||
public function header():void {
|
||||
if (!$this->is_action()) return;
|
||||
parent::header();
|
||||
}
|
||||
|
||||
#[Override]
|
||||
public function footer():void {
|
||||
if (!$this->is_action()) return;
|
||||
parent::footer();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+58
-5
@@ -33,6 +33,13 @@ final class Settings {
|
||||
public const LOCATION_WC_REGISTER = 'wc_register';
|
||||
public const LOCATION_WC_CHECKOUT = 'wc_checkout';
|
||||
|
||||
public const WIDGET_TYPE_STANDARD = 'standard';
|
||||
public const WIDGET_TYPE_FLOATING = 'floating';
|
||||
public const WIDGET_TYPES = [
|
||||
self::WIDGET_TYPE_STANDARD,
|
||||
self::WIDGET_TYPE_FLOATING,
|
||||
];
|
||||
|
||||
protected function __construct() {
|
||||
add_action('admin_menu', [$this, 'menu']);
|
||||
add_action('admin_init', [$this, 'init']);
|
||||
@@ -44,6 +51,10 @@ final class Settings {
|
||||
return $data;
|
||||
}
|
||||
|
||||
private static function has(string $key):bool {
|
||||
return array_key_exists($key, self::get());
|
||||
}
|
||||
|
||||
private static function set(string $key, $value = null):void {
|
||||
$data = self::get();
|
||||
if (is_null($value) && isset($data[$key])) {
|
||||
@@ -54,7 +65,8 @@ final class Settings {
|
||||
update_option(Plugin::get_id(), $data);
|
||||
}
|
||||
|
||||
private static function is_checked(string $name, string $value):bool {
|
||||
private static function is_checked(string $name, string $value, string|null $default = null):bool {
|
||||
if (!is_null($default) && !self::has($name)) return $value == $default;
|
||||
return in_array($value, (array) self::get($name));
|
||||
}
|
||||
|
||||
@@ -139,6 +151,16 @@ final class Settings {
|
||||
return self::is_form_location_enabled(self::LOCATION_WC_CHECKOUT);
|
||||
}
|
||||
|
||||
public static function get_widget_type():string {
|
||||
$value = (array) self::get('widget_type');
|
||||
$value = !empty($value) ? array_values($value)[0] : '';
|
||||
return in_array($value, self::WIDGET_TYPES) ? $value : self::WIDGET_TYPE_STANDARD;
|
||||
}
|
||||
|
||||
public static function is_widget_floating():bool {
|
||||
return self::get_widget_type() === self::WIDGET_TYPE_FLOATING;
|
||||
}
|
||||
|
||||
public function menu() {
|
||||
if (!current_user_can(self::CAPABILITY)) return;
|
||||
add_options_page(
|
||||
@@ -204,11 +226,11 @@ final class Settings {
|
||||
|
||||
add_settings_section(
|
||||
'forms',
|
||||
Plugin::__('CAPTCHA Forms'),
|
||||
Plugin::__('Forms'),
|
||||
[$this, 'section'],
|
||||
Plugin::get_id(),
|
||||
[
|
||||
'description' => Plugin::__('Define which forms you would like to secure with CAPTCHA.'),
|
||||
'description' => Plugin::__('Define which forms you would like to secure with Cap.'),
|
||||
]
|
||||
);
|
||||
|
||||
@@ -250,6 +272,36 @@ final class Settings {
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
add_settings_section(
|
||||
'widget',
|
||||
Plugin::__('Widget'),
|
||||
[$this, 'section'],
|
||||
Plugin::get_id(),
|
||||
[
|
||||
'description' => Plugin::__('Change how the Cap widget is displayed on the frontend.'),
|
||||
]
|
||||
);
|
||||
|
||||
add_settings_field(
|
||||
Plugin::get_id() . '_widget_type',
|
||||
Plugin::__('Widget type'),
|
||||
[$this, 'checkbox_field'],
|
||||
Plugin::get_id(),
|
||||
'widget',
|
||||
[
|
||||
'type' => 'radio',
|
||||
'name' => 'widget_type',
|
||||
'options' => [
|
||||
self::WIDGET_TYPE_STANDARD => Plugin::__('Standard (default)'),
|
||||
self::WIDGET_TYPE_FLOATING => Plugin::__('Floating'),
|
||||
],
|
||||
'default' => self::WIDGET_TYPE_STANDARD,
|
||||
'description' => Plugin::__('Not sure which type you want to use? <a href="https://trycap.dev/guide/demo.html" target="_blank">View the demo</a> to try them out.'),
|
||||
]
|
||||
);
|
||||
|
||||
|
||||
}
|
||||
|
||||
public function page() {
|
||||
@@ -334,11 +386,12 @@ final class Settings {
|
||||
|
||||
echo implode('<br>', array_map(
|
||||
fn (string $name, string $label):string => sprintf(
|
||||
'<label><input type="checkbox" name="%s[%s][]" value="%s" %s> %s</label>',
|
||||
'<label><input type="%s" name="%s[%s][]" value="%s" %s> %s</label>',
|
||||
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']),
|
||||
|
||||
+158
-6
@@ -12,12 +12,15 @@ namespace Ogre\Captcha;
|
||||
|
||||
use Ogre\Captcha as Plugin;
|
||||
use Ogre\Captcha\Settings;
|
||||
use WP_HTML_Tag_Processor;
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
final class Widget {
|
||||
|
||||
private const HANDLE = 'cap';
|
||||
private const FLOATING_HANDLE = 'cap-floating';
|
||||
private const DYNAMIC_FLOATING_HANDLE = 'cap-floating-dynamic';
|
||||
private const TOKEN_NAME = 'cap-token';
|
||||
|
||||
private static function get_script_url():string {
|
||||
@@ -25,28 +28,177 @@ final class Widget {
|
||||
return Settings::get_instance_url('assets/widget.js');
|
||||
}
|
||||
|
||||
public static function enqueue_assets():void {
|
||||
private static function get_floating_script_url():string {
|
||||
if (!Settings::is_connected()) return '';
|
||||
return Settings::get_instance_url('assets/floating.js');
|
||||
}
|
||||
|
||||
public static function enqueue_assets(bool $floating = false):void {
|
||||
if (!Settings::is_connected()) return;
|
||||
|
||||
if (!wp_script_is(self::HANDLE)) {
|
||||
wp_enqueue_script(self::HANDLE, self::get_script_url());
|
||||
wp_enqueue_script(
|
||||
self::HANDLE,
|
||||
self::get_script_url()
|
||||
);
|
||||
}
|
||||
|
||||
if ($floating && !wp_script_is(self::FLOATING_HANDLE)) {
|
||||
wp_enqueue_script(
|
||||
self::FLOATING_HANDLE,
|
||||
self::get_floating_script_url(),
|
||||
[
|
||||
self::HANDLE
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
if (!wp_style_is(self::HANDLE)) {
|
||||
wp_enqueue_style(self::HANDLE, Plugin::get_url('assets/widget.css'));
|
||||
wp_enqueue_style(
|
||||
self::HANDLE,
|
||||
Plugin::get_url('assets/widget.css')
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public static function render(string $id = 'cap'):void {
|
||||
public static function render(array $args = []):void {
|
||||
if (!Settings::is_connected()) return;
|
||||
self::enqueue_assets();
|
||||
|
||||
$args = wp_parse_args(
|
||||
$args,
|
||||
[
|
||||
'id' => 'cap',
|
||||
'floating' => Settings::is_widget_floating(),
|
||||
]
|
||||
);
|
||||
|
||||
self::enqueue_assets(!!$args['floating']);
|
||||
printf(
|
||||
'<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);
|
||||
while (true) {
|
||||
$found = false;
|
||||
foreach ($queries as $query) {
|
||||
if ($tags->next_tag($query)) {
|
||||
$found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!$found) break;
|
||||
|
||||
if (!is_null($args['type_name']) && !empty($args['type_name']) && $tags->get_attribute('type') !== $args['type_name']) continue;
|
||||
|
||||
foreach ($attributes as $name => $value) {
|
||||
$tags->set_attribute($name, $value);
|
||||
}
|
||||
}
|
||||
return $tags->get_updated_html();
|
||||
}
|
||||
|
||||
public static function add_dynamic_floating_attributes(array $args = []):void {
|
||||
if (wp_script_is(self::DYNAMIC_FLOATING_HANDLE)) return;
|
||||
|
||||
$args = self::parse_button_floating_args($args);
|
||||
|
||||
wp_enqueue_script(
|
||||
self::DYNAMIC_FLOATING_HANDLE,
|
||||
Plugin::get_url('assets/floating.js'),
|
||||
[
|
||||
self::FLOATING_HANDLE
|
||||
]
|
||||
);
|
||||
|
||||
wp_localize_script(
|
||||
self::DYNAMIC_FLOATING_HANDLE,
|
||||
'CAP_FLOATING_DYNAMIC',
|
||||
[
|
||||
'selector' => self::get_button_floating_selector($args),
|
||||
'attributes' => self::get_button_floating_attributes($args),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
public static function get_token():string {
|
||||
return isset($_POST[self::TOKEN_NAME]) ? (string) $_POST[self::TOKEN_NAME] : '';
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -12,17 +12,18 @@ namespace Ogre\Captcha\Location;
|
||||
|
||||
use Ogre\Captcha\API;
|
||||
use Override;
|
||||
use Ogre\Captcha\Location;
|
||||
use Ogre\Captcha\LoginLocation;
|
||||
use Ogre\Captcha\Settings;
|
||||
use Ogre\Captcha\Widget;
|
||||
use WP_Error;
|
||||
use WP_User;
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
final class LoginPage extends Location {
|
||||
final class LoginPage extends LoginLocation {
|
||||
|
||||
protected function __construct() {
|
||||
parent::__construct(Settings::LOCATION_LOGIN_PAGE);
|
||||
parent::__construct(Settings::LOCATION_LOGIN_PAGE, 'login');
|
||||
}
|
||||
|
||||
#[Override]
|
||||
@@ -34,6 +35,7 @@ final class LoginPage extends Location {
|
||||
#[Override]
|
||||
public function activate(): void
|
||||
{
|
||||
parent::activate();
|
||||
add_action('login_form', [$this, 'render']);
|
||||
add_filter('shake_error_codes', [$this, 'shake_error_codes']);
|
||||
add_filter('authenticate', [$this, 'process_form'], 20, 1);
|
||||
|
||||
@@ -11,17 +11,17 @@
|
||||
namespace Ogre\Captcha\Location;
|
||||
|
||||
use Override;
|
||||
use Ogre\Captcha\Location;
|
||||
use Ogre\Captcha\LoginLocation;
|
||||
use Ogre\Captcha\Settings;
|
||||
use WP_Error;
|
||||
use WP_User;
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
final class LostPasswordForm extends Location {
|
||||
final class LostPasswordForm extends LoginLocation {
|
||||
|
||||
protected function __construct() {
|
||||
parent::__construct(Settings::LOCATION_LOST_PASSWORD_FORM);
|
||||
parent::__construct(Settings::LOCATION_LOST_PASSWORD_FORM, 'lostpassword');
|
||||
}
|
||||
|
||||
#[Override]
|
||||
@@ -33,6 +33,7 @@ final class LostPasswordForm extends Location {
|
||||
#[Override]
|
||||
public function activate(): void
|
||||
{
|
||||
parent::activate();
|
||||
add_action('lostpassword_form', [$this, 'render']);
|
||||
add_action('lostpassword_post', [$this, 'process_form']);
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ namespace Ogre\Captcha\Location;
|
||||
use Override;
|
||||
use Ogre\Captcha\Location;
|
||||
use Ogre\Captcha\Settings;
|
||||
use Ogre\Captcha\Widget;
|
||||
use WP_Error;
|
||||
use WP_Post;
|
||||
|
||||
@@ -35,6 +36,11 @@ final class PostPasswordForm extends Location {
|
||||
ob_start();
|
||||
$this->render();
|
||||
$output .= ob_get_clean();
|
||||
|
||||
if (Settings::is_widget_floating()) {
|
||||
$output = Widget::set_button_floating($output);
|
||||
}
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
|
||||
@@ -11,16 +11,16 @@
|
||||
namespace Ogre\Captcha\Location;
|
||||
|
||||
use Override;
|
||||
use Ogre\Captcha\Location;
|
||||
use Ogre\Captcha\LoginLocation;
|
||||
use Ogre\Captcha\Settings;
|
||||
use WP_Error;
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
final class RegistrationPage extends Location {
|
||||
final class RegistrationPage extends LoginLocation {
|
||||
|
||||
protected function __construct() {
|
||||
parent::__construct(Settings::LOCATION_REGISTRATION_PAGE);
|
||||
parent::__construct(Settings::LOCATION_REGISTRATION_PAGE, 'register');
|
||||
}
|
||||
|
||||
#[Override]
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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']);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user