diff --git a/.distignore b/.distignore
index 7566f3a7..668ecdde 100644
--- a/.distignore
+++ b/.distignore
@@ -9,14 +9,13 @@
/.tests
/.wordpress-org
/.yarn
-/bin
/node_modules
/src/js
# Files
/.DS_Store
/.babelrc
-/.codeception.yml
+/codeception.yml
/.distignore
/.editorconfig
/.eslintrc.json
diff --git a/.tests/php/integration/AAAMainTest.php b/.tests/php/integration/AAAMainTest.php
index 5ddd283e..4a28c397 100644
--- a/.tests/php/integration/AAAMainTest.php
+++ b/.tests/php/integration/AAAMainTest.php
@@ -26,6 +26,7 @@
use HCaptcha\Jetpack\JetpackForm;
use HCaptcha\Main;
use HCaptcha\ElementorPro\HCaptchaHandler;
+use HCaptcha\Migrations\Migrations;
use HCaptcha\NF\NF;
use HCaptcha\Quform\Quform;
use HCaptcha\Sendinblue\Sendinblue;
@@ -109,15 +110,41 @@ public function test_init(): void {
$hcaptcha = hcaptcha();
// The plugin was loaded by codeception.
- self::assertSame( - PHP_INT_MAX, has_action( 'plugins_loaded', [ $hcaptcha, 'init_hooks' ] ) );
+ self::assertSame( Main::LOAD_PRIORITY, has_action( 'plugins_loaded', [ $hcaptcha, 'init_hooks' ] ) );
- remove_action( 'plugins_loaded', [ $hcaptcha, 'init_hooks' ], -PHP_INT_MAX );
+ remove_action( 'plugins_loaded', [ $hcaptcha, 'init_hooks' ], Main::LOAD_PRIORITY );
self::assertFalse( has_action( 'plugins_loaded', [ $hcaptcha, 'init_hooks' ] ) );
$hcaptcha->init();
- self::assertSame( - PHP_INT_MAX, has_action( 'plugins_loaded', [ $hcaptcha, 'init_hooks' ] ) );
+ self::assertSame( Main::LOAD_PRIORITY, has_action( 'plugins_loaded', [ $hcaptcha, 'init_hooks' ] ) );
+ }
+
+ /**
+ * Test init() on cron request.
+ *
+ * @return void
+ * @throws ReflectionException ReflectionException.
+ */
+ public function test_init_on_cron(): void {
+ $hcaptcha = hcaptcha();
+
+ // The plugin was loaded by codeception.
+ self::assertSame( Main::LOAD_PRIORITY, has_action( 'plugins_loaded', [ $hcaptcha, 'init_hooks' ] ) );
+
+ remove_action( 'plugins_loaded', [ $hcaptcha, 'init_hooks' ], Main::LOAD_PRIORITY );
+
+ self::assertFalse( has_action( 'plugins_loaded', [ $hcaptcha, 'init_hooks' ] ) );
+
+ add_filter( 'wp_doing_cron', '__return_true' );
+
+ $hcaptcha->init();
+
+ $migrations = $this->get_protected_property( $hcaptcha, 'migrations' );
+
+ self::assertFalse( has_action( 'plugins_loaded', [ $hcaptcha, 'init_hooks' ] ) );
+ self::assertSame( Migrations::LOAD_PRIORITY, has_action( 'plugins_loaded', [ $migrations, 'migrate' ] ) );
}
/**
@@ -166,9 +193,9 @@ static function () use ( $whitelisted ) {
)
);
- self::assertSame( - PHP_INT_MAX, has_action( 'plugins_loaded', [ $hcaptcha, 'init_hooks' ] ) );
+ self::assertSame( Main::LOAD_PRIORITY, has_action( 'plugins_loaded', [ $hcaptcha, 'init_hooks' ] ) );
- self::assertSame( - PHP_INT_MAX + 1, has_action( 'plugins_loaded', [ $hcaptcha, 'load_modules' ] ) );
+ self::assertSame( Main::LOAD_PRIORITY + 1, has_action( 'plugins_loaded', [ $hcaptcha, 'load_modules' ] ) );
self::assertSame( 10, has_filter( 'wp_resource_hints', [ $hcaptcha, 'prefetch_hcaptcha_dns' ] ) );
self::assertSame( 10, has_filter( 'wp_headers', [ $hcaptcha, 'csp_headers' ] ) );
@@ -188,11 +215,11 @@ static function () use ( $whitelisted ) {
$subject->init_hooks();
self::assertSame(
- - PHP_INT_MAX + 1,
+ Main::LOAD_PRIORITY + 1,
has_action( 'plugins_loaded', [ $subject, 'load_modules' ] )
);
self::assertSame(
- - PHP_INT_MAX,
+ -PHP_INT_MAX,
has_filter(
'hcap_whitelist_ip',
[ $subject, 'whitelist_ip' ]
@@ -285,11 +312,11 @@ static function () {
$subject->init_hooks();
self::assertSame(
- - PHP_INT_MAX + 1,
+ Main::LOAD_PRIORITY + 1,
has_action( 'plugins_loaded', [ $subject, 'load_modules' ] )
);
self::assertSame(
- - PHP_INT_MAX,
+ -PHP_INT_MAX,
has_filter(
'hcap_whitelist_ip',
[ $subject, 'whitelist_ip' ]
@@ -341,8 +368,12 @@ public function dp_test_init_and_init_hooks_on_elementor_pro_edit_page(): array
],
'request2' => [
'on',
- [ 'REQUEST_URI' => '/elementor?elementor-preview=23' ],
- [ 'elementor-preview' => 23 ],
+ [],
+ [
+ 'preview_id' => 23,
+ 'preview_nonce' => 'some',
+ 'preview' => true,
+ ],
[],
true,
],
@@ -613,7 +644,8 @@ public function dp_test_print_inline_styles(): array {
/**
* Test login_head().
*
- * @noinspection CssUnusedSymbol*/
+ * @noinspection CssUnusedSymbol
+ */
public function test_login_head(): void {
FunctionMocker::replace(
'defined',
@@ -930,7 +962,7 @@ function delayedLoad() {
'custom_themes' => $custom_themes ? [ $custom_themes ] : [],
// phpcs:ignore WordPress.WP.AlternativeFunctions.json_encode_json_encode
'config_params' => json_encode( $config_params ),
- 'delay' => - 100,
+ 'delay' => -100,
'license' => 'pro',
]
);
@@ -1302,11 +1334,38 @@ public function dp_test_load_modules(): array {
'back-in-stock-notifier-for-woocommerce/cwginstocknotifier.php',
\HCaptcha\BackInStockNotifier\Form::class,
],
+ 'bbPress Login Form' => [
+ [ 'bbp_status', 'login' ],
+ 'bbpress/bbpress.php',
+ [
+ \HCaptcha\BBPress\Login::class,
+ \HCaptcha\BBPress\LostPassword::class,
+ \HCaptcha\BBPress\Register::class,
+ ],
+ ],
+ 'bbPress Lost Password Form' => [
+ [ 'bbp_status', 'lost_pass' ],
+ 'bbpress/bbpress.php',
+ [
+ \HCaptcha\BBPress\Login::class,
+ \HCaptcha\BBPress\LostPassword::class,
+ \HCaptcha\BBPress\Register::class,
+ ],
+ ],
'bbPress New Topic' => [
[ 'bbp_status', 'new_topic' ],
'bbpress/bbpress.php',
NewTopic::class,
],
+ 'bbPress Register Form' => [
+ [ 'bbp_status', 'register' ],
+ 'bbpress/bbpress.php',
+ [
+ \HCaptcha\BBPress\Login::class,
+ \HCaptcha\BBPress\LostPassword::class,
+ \HCaptcha\BBPress\Register::class,
+ ],
+ ],
'bbPress Reply' => [
[ 'bbp_status', 'reply' ],
'bbpress/bbpress.php',
@@ -1696,7 +1755,7 @@ static function ( $override, $domain, $mofile ) use ( &$override_filter_params )
return $override;
},
- - PHP_INT_MAX,
+ Main::LOAD_PRIORITY,
3
);
diff --git a/.tests/php/integration/BBPress/LoginTest.php b/.tests/php/integration/BBPress/LoginTest.php
new file mode 100644
index 00000000..4167662e
--- /dev/null
+++ b/.tests/php/integration/BBPress/LoginTest.php
@@ -0,0 +1,104 @@
+
+ $placeholder
+
+HTML;
+
+ $args = [
+ 'action' => 'hcaptcha_login',
+ 'name' => 'hcaptcha_login_nonce',
+ 'id' => [
+ 'source' => [ 'bbpress/bbpress.php' ],
+ 'form_id' => 'login',
+ ],
+ ];
+ $hcaptcha = $this->get_hcap_form( $args );
+ $signature = HCaptcha::get_signature( Login::class, 'login', true );
+
+ $output = str_replace( $placeholder, '', $template );
+ $expected = str_replace( $placeholder, $hcaptcha . $signature . "\n", $template );
+
+ $subject = new Login();
+
+ // Wrong tag.
+ self::assertSame( $output, $subject->do_shortcode_tag( $output, 'some', $attr, $m ) );
+
+ // Logged in.
+ wp_set_current_user( 1 );
+
+ self::assertSame( $output, $subject->do_shortcode_tag( $output, $tag, $attr, $m ) );
+
+ // Login limit not exceeded.
+ wp_set_current_user( 0 );
+ add_filter(
+ 'hcap_login_limit_exceeded',
+ static function ( $value ) use ( &$limit_exceeded ) {
+ return $limit_exceeded;
+ }
+ );
+
+ $limit_exceeded = false;
+
+ self::assertSame( $output, $subject->do_shortcode_tag( $output, $tag, $attr, $m ) );
+
+ // Status is 'login'.
+ $limit_exceeded = true;
+
+ hcaptcha()->settings()->set( 'bbp_status', 'login' );
+
+ self::assertSame( $expected, $subject->do_shortcode_tag( $output, $tag, $attr, $m ) );
+ self::assertSame( 1, did_action( 'hcap_signature' ) );
+
+ // Status is not 'login'.
+ hcaptcha()->settings()->set( 'bbp_status', 'some' );
+
+ $expected = str_replace( $placeholder, $signature . "\n", $template );
+
+ self::assertSame( $expected, $subject->do_shortcode_tag( $output, $tag, $attr, $m ) );
+ self::assertSame( 2, did_action( 'hcap_signature' ) );
+ }
+}
diff --git a/.tests/php/integration/BBPress/LostPasswordTest.php b/.tests/php/integration/BBPress/LostPasswordTest.php
new file mode 100644
index 00000000..d31b27f4
--- /dev/null
+++ b/.tests/php/integration/BBPress/LostPasswordTest.php
@@ -0,0 +1,114 @@
+settings()->set( 'bbp_status', 'lost_pass' );
+
+ $subject = new LostPassword();
+
+ self::assertSame( 10, has_filter( 'do_shortcode_tag', [ $subject, 'add_captcha' ] ) );
+
+ hcaptcha()->settings()->set( 'bbp_status', 'some' );
+
+ $subject = new LostPassword();
+
+ self::assertSame( 10, has_filter( 'do_shortcode_tag', [ $subject, 'add_captcha' ] ) );
+ self::assertSame( 10, has_filter( 'hcap_protect_form', [ $subject, 'hcap_protect_form' ] ) );
+ }
+
+ /**
+ * Test add_captcha().
+ *
+ * @return void
+ */
+ public function test_add_captcha(): void {
+ $tag = 'bbp-lost-pass';
+ $attr = [];
+ $m = [];
+
+ $placeholder = '===hcaptcha placeholder===';
+ $template = <<
+ $placeholder
+
+HTML;
+
+ $args = [
+ 'action' => 'hcaptcha_action',
+ 'name' => 'hcaptcha_nonce',
+ 'auto' => true,
+ 'id' => [
+ 'source' => [ 'bbpress/bbpress.php' ],
+ 'form_id' => 'lost_password',
+ ],
+ ];
+ $hcaptcha = $this->get_hcap_form( $args );
+
+ $output = str_replace( $placeholder, '', $template );
+ $expected = str_replace( $placeholder, $hcaptcha . "\n", $template );
+
+ hcaptcha()->settings()->set( 'bbp_status', 'lost_pass' );
+ $subject = new LostPassword();
+
+ // Wrong tag.
+ self::assertSame( $output, $subject->add_captcha( $output, 'some', $attr, $m ) );
+
+ // Logged in.
+ wp_set_current_user( 1 );
+
+ self::assertSame( $output, $subject->add_captcha( $output, $tag, $attr, $m ) );
+
+ // Add hCaptcha..
+ wp_set_current_user( 0 );
+
+ add_action(
+ 'hcap_auto_verify_register',
+ static function ( $html ) use ( &$registered_output ) {
+ $registered_output = $html;
+ }
+ );
+
+ self::assertSame( $expected, $subject->add_captcha( $output, $tag, $attr, $m ) );
+ self::assertSame( 1, did_action( 'hcap_auto_verify_register' ) );
+ self::assertSame( $expected, $registered_output );
+ }
+
+ /**
+ * Test hcap_protect_form().
+ *
+ * @return void
+ */
+ public function test_hcap_protect_form(): void {
+ $source = [ 'bbpress/bbpress.php' ];
+ $form_id = 'lost_password';
+
+ $subject = new LostPassword();
+
+ self::assertTrue( $subject->hcap_protect_form( true, [ 'some source' ], $form_id ) );
+ self::assertTrue( $subject->hcap_protect_form( true, $source, 'some form id' ) );
+ self::assertFalse( $subject->hcap_protect_form( true, $source, $form_id ) );
+ }
+}
diff --git a/.tests/php/integration/BBPress/RegisterTest.php b/.tests/php/integration/BBPress/RegisterTest.php
new file mode 100644
index 00000000..48921fcd
--- /dev/null
+++ b/.tests/php/integration/BBPress/RegisterTest.php
@@ -0,0 +1,159 @@
+settings()->set( 'bbp_status', 'register' );
+
+ $subject = new Register();
+
+ self::assertSame( 10, has_filter( 'do_shortcode_tag', [ $subject, 'add_captcha' ] ) );
+ self::assertSame( 10, has_filter( 'registration_errors', [ $subject, 'verify' ] ) );
+
+ hcaptcha()->settings()->set( 'bbp_status', 'some' );
+
+ $subject = new Register();
+
+ self::assertSame( 10, has_filter( 'do_shortcode_tag', [ $subject, 'add_captcha' ] ) );
+ self::assertSame( 10, has_filter( 'hcap_protect_form', [ $subject, 'hcap_protect_form' ] ) );
+ }
+
+ /**
+ * Test add_captcha().
+ *
+ * @return void
+ */
+ public function test_add_captcha(): void {
+ $tag = 'bbp-register';
+ $attr = [];
+ $m = [];
+
+ $placeholder = '===hcaptcha placeholder===';
+ $template = <<
+ $placeholder
+
+HTML;
+
+ $args = [
+ 'action' => 'hcaptcha_bbp_register',
+ 'name' => 'hcaptcha_bbp_register_nonce',
+ 'id' => [
+ 'source' => [ 'bbpress/bbpress.php' ],
+ 'form_id' => 'register',
+ ],
+ ];
+ $hcaptcha = $this->get_hcap_form( $args );
+
+ $output = str_replace( $placeholder, '', $template );
+ $expected = str_replace( $placeholder, $hcaptcha . "\n", $template );
+
+ hcaptcha()->settings()->set( 'bbp_status', 'register' );
+ $subject = new Register();
+
+ // Wrong tag.
+ self::assertSame( $output, $subject->add_captcha( $output, 'some', $attr, $m ) );
+
+ // Logged in.
+ wp_set_current_user( 1 );
+
+ self::assertSame( $output, $subject->add_captcha( $output, $tag, $attr, $m ) );
+
+ // Status is 'register'.
+ wp_set_current_user( 0 );
+
+ $subject = new Register();
+
+ self::assertSame( $expected, $subject->add_captcha( $output, $tag, $attr, $m ) );
+
+ // Status is wrong.
+ $hcaptcha = $this->get_hcap_widget( $args['id'] );
+ $expected = str_replace( $placeholder, $hcaptcha . "\n\t\t\n", $template );
+
+ hcaptcha()->settings()->set( 'bbp_status', 'some' );
+ $subject = new Register();
+
+ self::assertSame( $expected, $subject->add_captcha( $output, $tag, $attr, $m ) );
+ }
+
+ /**
+ * Test verify().
+ *
+ * @return void
+ */
+ public function test_verify(): void {
+ $sanitized_user_login = 'login';
+ $user_email = 'email';
+ $errors = new WP_Error();
+ $expected = new WP_Error();
+
+ $errors->add( 'some code', 'some message' );
+ $expected->add( 'some code', 'some message' );
+
+ $this->prepare_hcaptcha_verify_post( 'hcaptcha_bbp_register_nonce', 'hcaptcha_bbp_register' );
+
+ $subject = new Register();
+
+ self::assertEquals( $expected, $subject->verify( $errors, $sanitized_user_login, $user_email ) );
+ }
+
+ /**
+ * Test verify() when not verified.
+ *
+ * @return void
+ */
+ public function test_verify_when_not_verified(): void {
+ $sanitized_user_login = 'login';
+ $user_email = 'email';
+ $errors = new WP_Error();
+ $expected = new WP_Error();
+
+ $errors->add( 'some code', 'some message' );
+ $expected->add( 'some code', 'some message' );
+ $expected->add( 'fail', 'The hCaptcha is invalid.' );
+
+ $subject = new Register();
+
+ $this->prepare_hcaptcha_verify_post( 'hcaptcha_bbp_register_nonce', 'hcaptcha_bbp_register', false );
+
+ self::assertEquals( $expected, $subject->verify( $errors, $sanitized_user_login, $user_email ) );
+ }
+
+ /**
+ * Test hcap_protect_form().
+ *
+ * @return void
+ */
+ public function test_hcap_protect_form(): void {
+ $source = [ 'bbpress/bbpress.php' ];
+ $form_id = 'register';
+
+ $subject = new Register();
+
+ self::assertTrue( $subject->hcap_protect_form( true, [ 'some source' ], $form_id ) );
+ self::assertTrue( $subject->hcap_protect_form( true, $source, 'some form id' ) );
+ self::assertFalse( $subject->hcap_protect_form( true, $source, $form_id ) );
+ }
+}
diff --git a/.tests/php/integration/CF7/AdminTest.php b/.tests/php/integration/CF7/AdminTest.php
index 00f93f24..99ead8c6 100644
--- a/.tests/php/integration/CF7/AdminTest.php
+++ b/.tests/php/integration/CF7/AdminTest.php
@@ -514,43 +514,4 @@ public function test_enqueue_admin_scripts(): void {
self::assertArrayHasKey( 'api', $wpcf7 );
}
-
- /**
- * Test is_cf7_form_admin_page().
- *
- * @param string $page Page.
- * @param bool $has_post Has post.
- * @param bool $expected Expected.
- *
- * @return void
- * @dataProvider dp_test_is_cf7_form_admin_page
- */
- public function test_is_cf7_form_admin_page( string $page, bool $has_post, bool $expected ): void {
- $subject = Mockery::mock( Admin::class )->makePartial();
- $subject->shouldAllowMockingProtectedMethods();
-
- $method = 'is_cf7_form_admin_page';
- $_GET['page'] = $page;
-
- if ( $has_post ) {
- $_GET['post'] = 177;
- }
-
- self::assertSame( $expected, $subject->$method( $expected ) );
- }
-
- /**
- * Data provider for test_is_cf7_form_admin_page().
- *
- * @return array
- */
- public function dp_test_is_cf7_form_admin_page(): array {
- return [
- [ 'some', false, false ],
- [ 'wpcf7', false, false ],
- [ 'wpcf7', true, true ],
- [ 'wpcf7-new', false, true ],
- [ 'wpcf7-new', true, true ],
- ];
- }
}
diff --git a/.tests/php/integration/CoBlocks/FormTest.php b/.tests/php/integration/CoBlocks/FormTest.php
new file mode 100644
index 00000000..412a3ae4
--- /dev/null
+++ b/.tests/php/integration/CoBlocks/FormTest.php
@@ -0,0 +1,312 @@
+ 'some block',
+ ];
+ $instance = Mockery::mock( WP_Block::class );
+ $template = <<
+
+
+HTML;
+
+ $subject = new Form();
+
+ $content = 'some content';
+
+ // Ignore non-form blocks.
+ self::assertSame( $content, $subject->add_hcaptcha( $content, $block, $instance ) );
+
+ $block = [
+ 'blockName' => 'coblocks/form',
+ ];
+
+ $args = [
+ 'action' => 'hcaptcha_coblocks',
+ 'name' => 'hcaptcha_coblocks_nonce',
+ 'id' => [
+ 'source' => [ 'coblocks/class-coblocks.php' ],
+ 'form_id' => $form_id,
+ ],
+ ];
+ $hcaptcha = $this->get_hcap_form( $args );
+
+ $content = str_replace( $placeholder, '', $template );
+ $expected = str_replace( $placeholder, $hcaptcha . "\n", $template );
+
+ self::assertSame( $expected, $subject->add_hcaptcha( $content, $block, $instance ) );
+ }
+
+ /**
+ * Test render_block_data().
+ *
+ * @return void
+ */
+ public function test_render_block_data(): void {
+ $parsed_block = [
+ 'blockName' => 'some',
+ 'attrs' => [ 'some' ],
+ ];
+ $source_block = [
+ 'blockName' => 'coblocks/form',
+ 'attrs' => [ 'some' ],
+ ];
+
+ $subject = new Form();
+
+ self::assertFalse( has_action( 'coblocks_before_form_submit', [ $subject, 'before_form_submit' ] ) );
+
+ // Ignore other blocks.
+ $subject->render_block_data( $parsed_block, $source_block );
+
+ self::assertFalse( has_action( 'coblocks_before_form_submit', [ $subject, 'before_form_submit' ] ) );
+
+ // Ignore for coblocks/form if no POST data.
+ $parsed_block['blockName'] = 'coblocks/form';
+
+ $subject->render_block_data( $parsed_block, $source_block );
+
+ self::assertFalse( has_action( 'coblocks_before_form_submit', [ $subject, 'before_form_submit' ] ) );
+
+ // Add action for coblocks/form.
+ $_POST['action'] = 'coblocks-form-submit';
+
+ $subject->render_block_data( $parsed_block, $source_block );
+
+ self::assertSame( 10, has_action( 'coblocks_before_form_submit', [ $subject, 'before_form_submit' ] ) );
+
+ // Do not add action for coblocks/form if already added.
+ remove_action( 'coblocks_before_form_submit', [ $subject, 'before_form_submit' ] );
+
+ $subject->render_block_data( $parsed_block, $source_block );
+
+ self::assertFalse( has_action( 'coblocks_before_form_submit', [ $subject, 'before_form_submit' ] ) );
+ }
+
+ /**
+ * Test before_form_submit().
+ *
+ * @return void
+ */
+ public function test_before_form_submit(): void {
+ $post = [ 'some' ];
+ $atts = [ 'some' ];
+
+ $subject = new Form();
+
+ $subject->before_form_submit( $post, $atts );
+
+ self::assertSame( 10, has_filter( 'pre_option_coblocks_google_recaptcha_site_key', '__return_true' ) );
+ self::assertSame( 10, has_filter( 'pre_option_coblocks_google_recaptcha_secret_key', '__return_true' ) );
+
+ // phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.ValidatedSanitizedInput.InputNotValidated, WordPress.Security.ValidatedSanitizedInput.MissingUnslash
+ self::assertSame( 'hcaptcha_token', $_POST['g-recaptcha-token'] );
+
+ self::assertSame( 10, has_filter( 'pre_http_request', [ $subject, 'verify' ] ) );
+ }
+
+ /**
+ * Test verify().
+ *
+ * @return void
+ */
+ public function test_verify(): void {
+ $verify_url = CoBlocks_Form::GCAPTCHA_VERIFY_URL;
+ $response = [ 'some response' ];
+ $parsed_args = [
+ 'body' => [
+ 'response' => 'some response',
+ ],
+ ];
+ $url = 'some url';
+ $expected = [
+ 'body' => '{"success":true}',
+ 'response' =>
+ [
+ 'code' => 200,
+ 'message' => 'OK',
+ ],
+ ];
+
+ $this->prepare_hcaptcha_verify_post( 'hcaptcha_coblocks_nonce', 'hcaptcha_coblocks' );
+
+ $subject = new Form();
+
+ add_filter( 'pre_http_request', [ $subject, 'verify' ] );
+
+ // Wrong url.
+ self::assertSame( $response, $subject->verify( $response, $parsed_args, $url ) );
+ self::assertSame( 10, has_filter( 'pre_http_request', [ $subject, 'verify' ] ) );
+
+ // Wrong token.
+ $url = $verify_url;
+
+ self::assertSame( $response, $subject->verify( $response, $parsed_args, $url ) );
+ self::assertSame( 10, has_filter( 'pre_http_request', [ $subject, 'verify' ] ) );
+
+ // Process verification.
+ $parsed_args['body']['response'] = 'hcaptcha_token';
+
+ self::assertSame( $expected, $subject->verify( $response, $parsed_args, $url ) );
+ self::assertFalse( has_filter( 'pre_http_request', [ $subject, 'verify' ] ) );
+ }
+
+ /**
+ * Test verify() when not verified.
+ *
+ * @return void
+ */
+ public function test_verify_not_verified(): void {
+ $verify_url = CoBlocks_Form::GCAPTCHA_VERIFY_URL;
+ $response = [ 'some response' ];
+ $parsed_args = [
+ 'body' => [
+ 'response' => 'hcaptcha_token',
+ ],
+ ];
+ $url = $verify_url;
+ $expected = [
+ 'body' => '{"success":false}',
+ 'response' =>
+ [
+ 'code' => 200,
+ 'message' => 'OK',
+ ],
+ ];
+
+ $this->prepare_hcaptcha_verify_post( 'hcaptcha_coblocks_nonce', 'hcaptcha_coblocks', false );
+
+ $subject = new Form();
+
+ add_filter( 'pre_http_request', [ $subject, 'verify' ] );
+
+ // Process verification.
+ $parsed_args['body']['response'] = 'hcaptcha_token';
+
+ self::assertSame( $expected, $subject->verify( $response, $parsed_args, $url ) );
+ self::assertFalse( has_filter( 'pre_http_request', [ $subject, 'verify' ] ) );
+ }
+
+ /**
+ * Test print_inline_styles().
+ *
+ * @return void
+ * @noinspection CssUnusedSymbol
+ */
+ public function test_print_inline_styles(): void {
+ FunctionMocker::replace(
+ 'defined',
+ static function ( $constant_name ) {
+ return 'SCRIPT_DEBUG' === $constant_name;
+ }
+ );
+
+ FunctionMocker::replace(
+ 'constant',
+ static function ( $name ) {
+ return 'SCRIPT_DEBUG' === $name;
+ }
+ );
+
+ $expected = <<\n$expected\n\n";
+
+ $subject = new Form();
+
+ // Show styles.
+ ob_start();
+
+ $subject->print_inline_styles();
+
+ self::assertSame( $expected, ob_get_clean() );
+ }
+}
diff --git a/.tests/php/integration/Divi/EmailOptinTest.php b/.tests/php/integration/Divi/EmailOptinTest.php
index 21019910..7a1646e8 100644
--- a/.tests/php/integration/Divi/EmailOptinTest.php
+++ b/.tests/php/integration/Divi/EmailOptinTest.php
@@ -20,6 +20,7 @@
* Class EmailOptinTest
*
* @group divi
+ * @group divi-email-optin
*/
class EmailOptinTest extends HCaptchaWPTestCase {
@@ -137,4 +138,35 @@ public function test_enqueue_scripts_when_form_was_not_shown(): void {
self::assertFalse( wp_script_is( EmailOptin::HANDLE ) );
}
+
+ /**
+ * Test add_type_module().
+ *
+ * @return void
+ * @noinspection JSUnresolvedLibraryURL
+ */
+ public function test_add_type_module(): void {
+ $subject = new EmailOptin();
+
+ // Wrong handle.
+
+ // phpcs:disable WordPress.WP.EnqueuedResources.NonEnqueuedScript
+ $tag = '';
+ $handle = 'some';
+ $src = 'https://example.com/script.js';
+
+ self::assertSame( $tag, $subject->add_type_module( $tag, $handle, $src ) );
+
+ // Proper handle.
+ $handle = EmailOptin::HANDLE;
+ $expected = '';
+
+ self::assertSame( $expected, $subject->add_type_module( $tag, $handle, $src ) );
+
+ // Script has a type.
+ $tag = '';
+ // phpcs:enable WordPress.WP.EnqueuedResources.NonEnqueuedScript
+
+ self::assertSame( $expected, $subject->add_type_module( $tag, $handle, $src ) );
+ }
}
diff --git a/.tests/php/integration/Helpers/PagesTest.php b/.tests/php/integration/Helpers/PagesTest.php
new file mode 100644
index 00000000..61f9ecd7
--- /dev/null
+++ b/.tests/php/integration/Helpers/PagesTest.php
@@ -0,0 +1,70 @@
+prepare_hcaptcha_get_verify_message( 'hcaptcha_jetpack_nonce', 'hcaptcha_jetpack', false );
@@ -73,6 +87,13 @@ public function test_jetpack_verify_not_verified(): void {
$subject = new JetpackForm();
self::assertEquals( $error, $subject->verify() );
+ self::assertNull( $this->get_protected_property( $subject, 'error_form_hash' ) );
+ self::assertSame( 10, has_action( 'hcap_hcaptcha_content', [ $subject, 'error_message' ] ) );
+
+ $_POST['contact-form-hash'] = $hash;
+
+ self::assertEquals( $error, $subject->verify() );
+ self::assertSame( $hash, $this->get_protected_property( $subject, 'error_form_hash' ) );
self::assertSame( 10, has_action( 'hcap_hcaptcha_content', [ $subject, 'error_message' ] ) );
}
@@ -85,6 +106,12 @@ public function test_jetpack_verify_not_verified(): void {
public function test_error_message(): void {
$hcaptcha_content = 'some content';
$error_message = 'some error message';
+ $error_form_hash = 'some hash';
+ $args = [
+ 'id' => [
+ 'form_id' => $error_form_hash,
+ ],
+ ];
$subject = new JetpackForm();
@@ -92,6 +119,10 @@ public function test_error_message(): void {
$this->set_protected_property( $subject, 'error_message', $error_message );
+ self::assertSame( $hcaptcha_content, $subject->error_message( $hcaptcha_content ) );
+
+ $this->set_protected_property( $subject, 'error_form_hash', $error_form_hash );
+
$expected = $hcaptcha_content . '
Warning.
@@ -100,7 +131,7 @@ public function test_error_message(): void {
' . $error_message . '
';
- self::assertSame( $expected, $subject->error_message( $hcaptcha_content ) );
+ self::assertSame( $expected, $subject->error_message( $hcaptcha_content, $args ) );
}
/**
diff --git a/.tests/php/integration/Jetpack/JetpackFormTest.php b/.tests/php/integration/Jetpack/JetpackFormTest.php
index a57d8135..18fb99e1 100644
--- a/.tests/php/integration/Jetpack/JetpackFormTest.php
+++ b/.tests/php/integration/Jetpack/JetpackFormTest.php
@@ -40,39 +40,49 @@ public function test_add_captcha( string $content, string $expected ): void {
public function dp_test_add_captcha(): array {
$_SERVER['REQUEST_URI'] = 'http://test.test/';
- $args = [
+ $hash = 'some hash';
+ $hash_input = "";
+ $classic_args = [
'action' => 'hcaptcha_jetpack',
'name' => 'hcaptcha_jetpack_nonce',
- 'force' => true,
'id' => [
'source' => [ 'jetpack/jetpack.php' ],
'form_id' => 'contact',
],
];
- $hcaptcha = $this->get_hcap_form( $args );
+ $classic_hcaptcha = $this->get_hcap_form( $classic_args );
+ $args = [
+ 'action' => 'hcaptcha_jetpack',
+ 'name' => 'hcaptcha_jetpack_nonce',
+ 'id' => [
+ 'source' => [ 'jetpack/jetpack.php' ],
+ 'form_id' => 'contact_' . $hash,
+ ],
+ ];
+ $hcaptcha = $this->get_hcap_form( $args );
return [
'Empty contact form' => [ '', '' ],
'Classic contact form' => [
- '[contact-form] Some contact form [/contact-form]',
- '[contact-form] Some contact form ' . $hcaptcha . '
[/contact-form]',
+ '',
+ '',
],
'Classic contact form with hcaptcha' => [
'[contact-form] Some contact form [hcaptcha][/contact-form]',
'[contact-form] Some contact form [hcaptcha][/contact-form]',
],
'Block contact form' => [
- '',
- '',
+ 'Contact Us' . $hash_input . '',
],
'Block contact form with hcaptcha' => [
'
',
'',
],
'Block contact form and search form' => [
- '' .
+ '' .
'',
- 'Contact Us' .
+ '
Contact Us' . $hash_input . '' .
'
',
],
];
diff --git a/.tests/php/integration/MailPoet/FormTest.php b/.tests/php/integration/MailPoet/FormTest.php
new file mode 100644
index 00000000..22a90335
--- /dev/null
+++ b/.tests/php/integration/MailPoet/FormTest.php
@@ -0,0 +1,169 @@
+
+
+ $placeholder
+
+
+
+HTML;
+
+ $subject = new Form();
+
+ $content = 'some content';
+ self::assertSame( $content, $subject->the_content_filter( $content ) );
+
+ $args = [
+ 'action' => 'hcaptcha_mailpoet',
+ 'name' => 'hcaptcha_mailpoet_nonce',
+ 'id' => [
+ 'source' => [ 'mailpoet/mailpoet.php' ],
+ 'form_id' => $form_id,
+ ],
+ ];
+ $hcaptcha = $this->get_hcap_form( $args );
+
+ $content = str_replace( $placeholder, '', $template );
+ $expected = str_replace( $placeholder, $hcaptcha, $template );
+
+ self::assertSame( $expected, $subject->the_content_filter( $content ) );
+ }
+
+ /**
+ * Test verify().
+ */
+ public function test_verify(): void {
+ $api = Mockery::mock( API::class );
+
+ $subject = new Form();
+
+ $subject->verify( $api );
+
+ $_POST['action'] = 'mailpoet';
+ $_POST['endpoint'] = 'subscribers';
+ $_POST['method'] = 'subscribe';
+
+ $this->prepare_hcaptcha_verify_post( 'hcaptcha_mailpoet_nonce', 'hcaptcha_mailpoet' );
+
+ $subject->verify( $api );
+ }
+
+ /**
+ * Test verify() when not verified.
+ */
+ public function test_verify_not_verified(): void {
+ $code = 'fail';
+ $error_message = 'The hCaptcha is invalid.';
+
+ Mockery::namedMock( Response::class, ResponseStub::class );
+ $error_response = Mockery::mock( ErrorResponse::class );
+ $api = Mockery::mock( API::class );
+
+ $error_response->shouldReceive( 'send' )->once();
+ $api->shouldReceive( 'createErrorResponse' )
+ ->with( $code, $error_message, ResponseStub::STATUS_UNAUTHORIZED )
+ ->andReturn( $error_response );
+
+ $subject = new Form();
+
+ $subject->verify( $api );
+
+ $_POST['action'] = 'mailpoet';
+ $_POST['endpoint'] = 'subscribers';
+ $_POST['method'] = 'subscribe';
+
+ $this->prepare_hcaptcha_verify_post( 'hcaptcha_mailpoet_nonce', 'hcaptcha_mailpoet', false );
+
+ $subject->verify( $api );
+ }
+
+ /**
+ * Test enqueue_scripts().
+ */
+ public function test_enqueue_scripts(): void {
+ $subject = new Form();
+
+ self::assertFalse( wp_script_is( 'hcaptcha-mailpoet' ) );
+
+ $subject->enqueue_scripts();
+
+ self::assertFalse( wp_script_is( 'hcaptcha-mailpoet' ) );
+
+ hcaptcha()->form_shown = true;
+
+ $subject->enqueue_scripts();
+
+ self::assertTrue( wp_script_is( 'hcaptcha-mailpoet' ) );
+ }
+}
diff --git a/.tests/php/integration/Mailchimp/FormTest.php b/.tests/php/integration/Mailchimp/FormTest.php
index 9c4f423e..0cd827d3 100644
--- a/.tests/php/integration/Mailchimp/FormTest.php
+++ b/.tests/php/integration/Mailchimp/FormTest.php
@@ -18,6 +18,8 @@
/**
* Test Form class.
+ *
+ * @group mailchimp
*/
class FormTest extends HCaptchaWPTestCase {
diff --git a/.tests/php/integration/MainPluginFileTest.php b/.tests/php/integration/MainPluginFileTest.php
index 16109bb0..5658e16b 100644
--- a/.tests/php/integration/MainPluginFileTest.php
+++ b/.tests/php/integration/MainPluginFileTest.php
@@ -68,6 +68,10 @@ public function test_main_file_content(): void {
* Test that readme.txt contains proper stable tag.
*/
public function test_stable_tag_in_readme_txt(): void {
+ if ( preg_match( '/-.+$/', HCAPTCHA_VERSION ) ) {
+ $this->markTestSkipped( 'Not a final version, skipping stable tag in readme.txt test.' );
+ }
+
$expected = [
'stable_tag' => HCAPTCHA_VERSION,
];
@@ -86,6 +90,10 @@ public function test_stable_tag_in_readme_txt(): void {
* Test that readme.txt contains changelog records for the current version.
*/
public function test_changelog(): void {
+ if ( preg_match( '/-.+$/', HCAPTCHA_VERSION ) ) {
+ $this->markTestSkipped( 'Not a final version, skipping changelog test.' );
+ }
+
$readme_file = HCAPTCHA_PATH . '/readme.txt';
$changelog_file = HCAPTCHA_PATH . '/changelog.txt';
diff --git a/.tests/php/integration/Migrations/MigrationsTest.php b/.tests/php/integration/Migrations/MigrationsTest.php
index 070d9271..0451af9f 100644
--- a/.tests/php/integration/Migrations/MigrationsTest.php
+++ b/.tests/php/integration/Migrations/MigrationsTest.php
@@ -9,6 +9,7 @@
use HCaptcha\Admin\Events\Events;
use HCaptcha\Migrations\Migrations;
+use HCaptcha\Settings\PluginSettingsBase;
use HCaptcha\Tests\Integration\HCaptchaWPTestCase;
use Mockery;
use ReflectionException;
@@ -29,6 +30,7 @@ public function tearDown(): void {
parent::tearDown();
}
+
/**
* Test init() and init_hooks().
*
@@ -62,7 +64,7 @@ public function dp_test_init_and_init_hooks(): array {
return [
[ false, false, false ],
[ true, false, false ],
- [ false, true, - PHP_INT_MAX ],
+ [ false, true, -PHP_INT_MAX ],
[ true, true, false ],
];
}
@@ -122,6 +124,8 @@ public function test_migrate(): void {
$subject->migrate();
+ self::assertSame( 10, has_action( 'init', [ $subject, 'send_plugin_stats' ] ) );
+
self::assertTrue( $this->compare_migrated( $expected_option, get_option( $subject::MIGRATED_VERSIONS_OPTION_NAME, [] ) ) );
self::assertSame( $expected_settings, get_option( 'hcaptcha_settings', [] ) );
self::assertFalse( get_option( 'hcaptcha_size' ) );
@@ -158,6 +162,21 @@ private function compare_migrated( array $expected_option, array $option ): bool
return true;
}
+ /**
+ * Test send_plugin_stats().
+ *
+ * @return void
+ */
+ public function test_send_plugin_stats(): void {
+ $subject = new Migrations();
+
+ self::assertSame( 0, did_action( 'hcap_send_plugin_stats' ) );
+
+ $subject->send_plugin_stats();
+
+ self::assertSame( 1, did_action( 'hcap_send_plugin_stats' ) );
+ }
+
/**
* Test migrate_360() when WPForms status not set.
*
@@ -224,4 +243,88 @@ static function ( $queries ) use ( &$actual_query ) {
self::assertSame( array_filter( explode( ';', $expected_query ) ), $actual_query );
}
+
+ /**
+ * Test save_license_level().
+ *
+ * @param string $license_level License level.
+ *
+ * @return void
+ * @dataProvider dp_test_save_license_level
+ */
+ public function test_save_license_level( string $license_level ): void {
+ $subject = new Migrations();
+
+ $option = get_option( PluginSettingsBase::OPTION_NAME, [] );
+
+ self::assertSame( [], $option );
+
+ switch ( $license_level ) {
+ case 'free':
+ $result = [
+ 'features' => [],
+ 'pass' => true,
+ ];
+ $expected = [
+ 'license' => $license_level,
+ ];
+
+ break;
+ case 'pro':
+ $result = [
+ 'features' => [
+ 'custom_theme' => [ 'some theme' ],
+ ],
+ 'pass' => true,
+ ];
+ $expected = [
+ 'license' => $license_level,
+ ];
+
+ break;
+ case 'error':
+ $result['pass'] = false;
+ $result['error'] = 'some error';
+ $expected = [];
+
+ break;
+ default:
+ $result = [];
+ $expected = [];
+
+ break;
+ }
+
+ add_filter(
+ 'pre_http_request',
+ static function ( $value, $parsed_args, $url ) use ( $result ) {
+ if ( false !== strpos( $url, 'hcaptcha.com' ) ) {
+ return [
+ 'body' => wp_json_encode( $result ),
+ ];
+ }
+
+ return $value;
+ },
+ 10,
+ 3
+ );
+
+ $subject->save_license_level();
+
+ self::assertSame( $expected, get_option( PluginSettingsBase::OPTION_NAME, [] ) );
+ }
+
+ /**
+ * Data provider for test_save_license_level().
+ *
+ * @return array
+ */
+ public function dp_test_save_license_level(): array {
+ return [
+ [ 'free' ],
+ [ 'pro' ],
+ [ 'error' ],
+ ];
+ }
}
diff --git a/.tests/php/integration/NF/FieldTest.php b/.tests/php/integration/NF/FieldTest.php
index 20decfc5..ef934d05 100644
--- a/.tests/php/integration/NF/FieldTest.php
+++ b/.tests/php/integration/NF/FieldTest.php
@@ -15,7 +15,8 @@
*
* Ninja Forms requires PHP 7.2.
*
- * @requires PHP <= 8.2
+ * @requires PHP < 8.3
+ * @group nf
*/
class FieldTest extends HCaptchaPluginWPTestCase {
@@ -35,6 +36,7 @@ public function test_constructor(): void {
$subject = new Field();
self::assertSame( 'hCaptcha', $subject->get_nicename() );
+ self::assertSame( 10, has_filter( 'nf_sub_hidden_field_types', [ $subject, 'hide_field_type' ] ) );
}
/**
@@ -69,4 +71,18 @@ public function test_validate_not_validated(): void {
self::assertSame( 'The hCaptcha is invalid.', $subject->validate( $field, null ) );
}
+
+ /**
+ * Test hide_field_type().
+ *
+ * @return void
+ */
+ public function test_hide_field_type(): void {
+ $hidden_field_types = [ 'some type' ];
+ $expected = [ 'some type', 'hcaptcha-for-ninja-forms' ];
+
+ $subject = new Field();
+
+ self::assertSame( $expected, $subject->hide_field_type( $hidden_field_types ) );
+ }
}
diff --git a/.tests/php/integration/NF/NFTest.php b/.tests/php/integration/NF/NFTest.php
index 3f3a7eb3..0605444c 100644
--- a/.tests/php/integration/NF/NFTest.php
+++ b/.tests/php/integration/NF/NFTest.php
@@ -7,9 +7,11 @@
namespace HCaptcha\Tests\Integration\NF;
+use HCaptcha\NF\Base;
use HCaptcha\NF\Field;
use HCaptcha\NF\NF;
use HCaptcha\Tests\Integration\HCaptchaPluginWPTestCase;
+use ReflectionException;
use tad\FunctionMocker\FunctionMocker;
/**
@@ -17,7 +19,8 @@
*
* Ninja Forms requires PHP 7.2.
*
- * @requires PHP <= 8.2
+ * @requires PHP < 8.3
+ * @group nf
*/
class NFTest extends HCaptchaPluginWPTestCase {
@@ -29,26 +32,218 @@ class NFTest extends HCaptchaPluginWPTestCase {
protected static $plugin = 'ninja-forms/ninja-forms.php';
/**
- * Test init_hooks().
+ * Tear down the test.
+ *
+ * @return void
+ */
+ public function tearDown(): void {
+ unset( $_GET['form_id'] );
+
+ parent::tearDown();
+ }
+
+ /**
+ * Test init() and init_hooks().
+ *
+ * @throws ReflectionException ReflectionException.
*/
- public function test_init_hooks(): void {
+ public function test_init_and_init_hooks(): void {
$subject = new NF();
+ self::assertSame(
+ str_replace( '\\', '/', HCAPTCHA_PATH . '/src/php/NF/templates/' ),
+ str_replace( '\\', '/', $this->get_protected_property( $subject, 'templates_dir' ) )
+ );
+
+ self::assertSame(
+ 11,
+ has_filter( 'toplevel_page_ninja-forms', [ $subject, 'admin_template' ] )
+ );
+ self::assertSame(
+ 10,
+ has_filter( 'nf_admin_enqueue_scripts', [ $subject, 'nf_admin_enqueue_scripts' ] )
+ );
self::assertSame(
10,
has_filter( 'ninja_forms_register_fields', [ $subject, 'register_fields' ] )
);
+ self::assertSame(
+ 10,
+ has_filter( 'ninja_forms_loaded', [ $subject, 'place_hcaptcha_before_recaptcha_field' ] )
+ );
self::assertSame(
10,
has_filter( 'ninja_forms_field_template_file_paths', [ $subject, 'template_file_paths' ] )
);
+ self::assertSame(
+ 10,
+ has_filter( 'nf_get_form_id', [ $subject, 'set_form_id' ] )
+ );
self::assertSame(
10,
has_filter( 'ninja_forms_localize_field_hcaptcha-for-ninja-forms', [ $subject, 'localize_field' ] )
);
+ self::assertSame(
+ 10,
+ has_filter( 'ninja_forms_localize_field_hcaptcha-for-ninja-forms_preview', [ $subject, 'localize_field' ] )
+ );
self::assertSame( 9, has_action( 'wp_print_footer_scripts', [ $subject, 'nf_captcha_script' ] ) );
}
+ /**
+ * Test admin_template().
+ *
+ * @return void
+ */
+ public function test_admin_template(): void {
+ $subject = new NF();
+
+ ob_start();
+ $subject->admin_template();
+ self::assertSame( '', ob_get_clean() );
+
+ $_GET['form_id'] = 'some';
+ $templates_dir = $this->get_protected_property( $subject, 'templates_dir' );
+
+ // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents
+ $template = file_get_contents( $templates_dir . 'fields-hcaptcha.html' );
+ $expected = str_replace(
+ 'tmpl-nf-field-' . Base::TYPE,
+ 'tmpl-nf-field-' . Base::NAME,
+ $template
+ );
+
+ ob_start();
+ $subject->admin_template();
+ self::assertSame( $expected, ob_get_clean() );
+ }
+
+ /**
+ * Test nf_admin_enqueue_scripts().
+ *
+ * @return void
+ * @throws ReflectionException ReflectionException.
+ */
+ public function test_nf_admin_enqueue_scripts(): void {
+ global $wp_scripts;
+
+ $subject = new NF();
+
+ $subject->nf_admin_enqueue_scripts();
+
+ self::assertFalse( wp_script_is( 'hcaptcha-nf' ) );
+
+ $field_id = 27;
+ $hcaptcha_id = 'hcaptcha-nf-625d3b9b318fc0.86180601';
+ $form_data = <<
[
+ 'source' => 'ninja-forms/ninja-forms.php',
+ 'form_id' => $form_id,
+ ],
+ ];
+
+ $hcaptcha = $this->get_hcap_form( $args );
+ $hcaptcha = str_replace(
+ 'set_protected_property( $subject, 'form_id', $form_id );
+
+ FunctionMocker::replace( 'uniqid', $hcaptcha_id );
+
+ $subject->nf_admin_enqueue_scripts();
+
+ $data = $wp_scripts->registered['nf-builder']->extra['data'];
+
+ preg_match( '/var nfDashInlineVars = (.+);/', $data, $m );
+
+ self::assertSame( $expected, json_decode( $m[1], true ) );
+
+ self::assertTrue( wp_script_is( 'kagg-dialog' ) );
+ self::assertTrue( wp_style_is( 'kagg-dialog' ) );
+ self::assertTrue( wp_script_is( 'admin-nf' ) );
+
+ $data = $wp_scripts->registered['admin-nf']->extra['data'];
+
+ preg_match( '/var HCaptchaAdminNFObject = ({.+});/', $data, $m );
+
+ $admin_nf_obj = json_decode( $m[1], true );
+ self::assertSame(
+ [
+ 'onlyOne' => 'Only one hCaptcha field allowed.',
+ 'OKBtnText' => 'OK',
+ ],
+ $admin_nf_obj
+ );
+ }
+
/**
* Test register_fields.
*/
@@ -60,6 +255,31 @@ public function test_register_fields(): void {
self::assertInstanceOf( Field::class, $fields['hcaptcha-for-ninja-forms'] );
}
+ /**
+ * Test place_hcaptcha_before_recaptcha_field().
+ *
+ * @return void
+ */
+ public function test_place_hcaptcha_before_recaptcha_field(): void {
+ $hcaptcha_key = Base::NAME;
+
+ $fields = Ninja_Forms()->fields;
+ $hcap_index = array_search( $hcaptcha_key, array_keys( $fields ), true );
+
+ self::assertFalse( $hcap_index );
+
+ $subject = new NF();
+
+ Ninja_Forms()->fields = $subject->register_fields( Ninja_Forms()->fields );
+ $subject->place_hcaptcha_before_recaptcha_field();
+
+ $fields = Ninja_Forms()->fields;
+ $hcap_index = array_search( $hcaptcha_key, array_keys( $fields ), true );
+ $recap_index = array_search( 'recaptcha', array_keys( $fields ), true );
+
+ self::assertSame( $recap_index, $hcap_index + 1 );
+ }
+
/**
* Test template_file_paths().
*/
@@ -78,6 +298,24 @@ static function ( &$item ) {
self::assertSame( $expected, $paths );
}
+ /**
+ * Test set_form_id().
+ *
+ * @return void
+ * @throws ReflectionException ReflectionException.
+ */
+ public function test_set_form_id(): void {
+ $form_id = 23;
+
+ $subject = new NF();
+
+ self::assertSame( 0, $this->get_protected_property( $subject, 'form_id' ) );
+
+ $subject->set_form_id( $form_id );
+
+ self::assertSame( $form_id, $this->get_protected_property( $subject, 'form_id' ) );
+ }
+
/**
* Test localize_field().
*/
diff --git a/.tests/php/integration/Spectra/FormTest.php b/.tests/php/integration/Spectra/FormTest.php
new file mode 100644
index 00000000..2736dd9e
--- /dev/null
+++ b/.tests/php/integration/Spectra/FormTest.php
@@ -0,0 +1,418 @@
+ 'some block',
+ ];
+ $instance = Mockery::mock( WP_Block::class );
+ $template = <<
+
+
The form has been submitted successfully!
+
+
There has been some error while submitting the form. Please verify all form fields again.
+
+
+HTML;
+
+ $subject = new Form();
+
+ $content = 'some content';
+
+ // Ignore non-form blocks.
+ self::assertSame( $content, $subject->render_block( $content, $block, $instance ) );
+
+ $content = str_replace( $placeholder, 'uagb-forms-recaptcha', $template );
+ $block = [
+ 'blockName' => 'uagb/forms',
+ 'attrs' => [
+ 'block_id' => $form_id,
+ ],
+ ];
+
+ // Do not replace reCaptcha.
+ self::assertSame( $content, $subject->render_block( $content, $block, $instance ) );
+
+ $args = [
+ 'action' => 'hcaptcha_spectra_form',
+ 'name' => 'hcaptcha_spectra_form_nonce',
+ 'id' => [
+ 'source' => [ 'ultimate-addons-for-gutenberg/ultimate-addons-for-gutenberg.php' ],
+ 'form_id' => $form_id,
+ ],
+ ];
+ $hcaptcha = $this->get_hcap_form( $args );
+
+ $content = str_replace( $placeholder, '', $template );
+ $expected = str_replace( $placeholder, $hcaptcha, $template );
+
+ self::assertSame( $expected, $subject->render_block( $content, $block, $instance ) );
+ }
+
+ /**
+ * Test process_ajax().
+ *
+ * @return void
+ */
+ public function test_process_ajax(): void {
+ $nonce_field_name = 'hcaptcha_spectra_form_nonce';
+ $nonce_action_name = 'hcaptcha_spectra_form';
+ $hcaptcha_response = 'some response';
+ $form_data = wp_json_encode(
+ [
+ 'h-captcha-response' => $hcaptcha_response,
+ $nonce_field_name => wp_create_nonce( $nonce_action_name ),
+ ]
+ );
+ $_POST['form_data'] = $form_data;
+
+ $this->prepare_hcaptcha_request_verify( $hcaptcha_response );
+
+ add_filter( 'wp_doing_ajax', '__return_true' );
+
+ $subject = Mockery::mock( Form::class )->makePartial();
+
+ $subject->shouldAllowMockingProtectedMethods();
+ $subject->shouldReceive( 'has_recaptcha' )->andReturn( false );
+
+ $subject->process_ajax();
+ }
+
+ /**
+ * Test process_ajax() when not verified.
+ *
+ * @return void
+ */
+ public function test_process_ajax_when_not_verified(): void {
+ $nonce_field_name = 'hcaptcha_spectra_form_nonce';
+ $nonce_action_name = 'hcaptcha_spectra_form';
+ $hcaptcha_response = 'some response';
+ $form_data = wp_json_encode(
+ [
+ 'h-captcha-response' => $hcaptcha_response,
+ $nonce_field_name => wp_create_nonce( $nonce_action_name ),
+ ]
+ );
+ $response = [
+ 'success' => false,
+ 'data' => 400,
+ ];
+ $expected = [
+ '',
+ '',
+ [ 'response' => null ],
+ ];
+
+ $this->prepare_hcaptcha_request_verify( $hcaptcha_response, false );
+
+ add_filter( 'wp_doing_ajax', '__return_true' );
+ add_filter(
+ 'wp_die_ajax_handler',
+ static function ( $name ) use ( &$die_arr ) {
+ return static function ( $message, $title, $args ) use ( &$die_arr ) {
+ $die_arr = [ $message, $title, $args ];
+ };
+ }
+ );
+
+ $subject = Mockery::mock( Form::class )->makePartial();
+
+ $subject->shouldAllowMockingProtectedMethods();
+ $subject->shouldReceive( 'has_recaptcha' )->andReturn( false );
+
+ // Without form_data.
+ ob_start();
+
+ $subject->process_ajax();
+
+ // phpcs:ignore WordPress.WP.AlternativeFunctions.json_encode_json_encode
+ self::assertSame( json_encode( $response ), ob_get_clean() );
+
+ self::assertSame( $expected, $die_arr );
+
+ // With form_data.
+ $_POST['form_data'] = $form_data;
+
+ ob_start();
+
+ $subject->process_ajax();
+
+ // phpcs:ignore WordPress.WP.AlternativeFunctions.json_encode_json_encode
+ self::assertSame( json_encode( $response ), ob_get_clean() );
+
+ self::assertSame( $expected, $die_arr );
+ }
+
+ /**
+ * Test process_ajax() when has recaptcha.
+ *
+ * @return void
+ */
+ public function test_process_ajax_when_has_recaptcha(): void {
+ $subject = Mockery::mock( Form::class )->makePartial();
+
+ $subject->shouldAllowMockingProtectedMethods();
+ $subject->shouldReceive( 'has_recaptcha' )->andReturn( true );
+
+ $subject->process_ajax();
+ }
+
+ /**
+ * Test print_inline_styles().
+ *
+ * @return void
+ * @noinspection CssUnusedSymbol
+ */
+ public function test_print_inline_styles(): void {
+ FunctionMocker::replace(
+ 'defined',
+ static function ( $constant_name ) {
+ return 'SCRIPT_DEBUG' === $constant_name;
+ }
+ );
+
+ FunctionMocker::replace(
+ 'constant',
+ static function ( $name ) {
+ return 'SCRIPT_DEBUG' === $name;
+ }
+ );
+
+ $expected = <<\n$expected\n\n";
+
+ $subject = new Form();
+
+ // Show styles.
+ ob_start();
+
+ $subject->print_inline_styles();
+
+ self::assertSame( $expected, ob_get_clean() );
+
+ // No output if already shown.
+ ob_start();
+
+ $subject->print_inline_styles();
+
+ self::assertSame( '', ob_get_clean() );
+ }
+
+ /**
+ * Test print_hcaptcha_scripts().
+ *
+ * @return void
+ * @noinspection PhpConditionAlreadyCheckedInspection
+ * @throws ReflectionException ReflectionException.
+ */
+ public function test_print_hcaptcha_scripts(): void {
+ $subject = new Form();
+
+ self::assertTrue( $subject->print_hcaptcha_scripts( false ) );
+
+ $this->set_protected_property( $subject, 'has_recaptcha_field', true );
+
+ self::assertFalse( $subject->print_hcaptcha_scripts( false ) );
+ }
+
+ /**
+ * Test enqueue_scripts().
+ */
+ public function test_enqueue_scripts(): void {
+ $subject = new Form();
+
+ self::assertFalse( wp_script_is( 'hcaptcha-spectra' ) );
+
+ $subject->enqueue_scripts();
+
+ self::assertTrue( wp_script_is( 'hcaptcha-spectra' ) );
+ }
+
+ /**
+ * Test has_recaptcha().
+ *
+ * @return void
+ */
+ public function test_has_recaptcha() {
+ $block_id = 'f89cebda';
+ $recaptcha_placeholder = '=== recaptcha placeholder ===';
+ $template = <<
+
+
+HTML;
+
+ $subject = Mockery::mock( Form::class )->makePartial();
+ $subject->shouldAllowMockingProtectedMethods();
+
+ self::assertFalse( $subject->has_recaptcha() );
+
+ $post_content = str_replace( $recaptcha_placeholder, '', $template );
+ $post_id = wp_insert_post( [ 'post_content' => $post_content ] );
+ $_POST['post_id'] = $post_id;
+ $_POST['block_id'] = $block_id;
+
+ self::assertFalse( $subject->has_recaptcha() );
+
+ $post_content = str_replace( $recaptcha_placeholder, '"reCaptchaEnable":"true",', $template );
+ $post_id = wp_insert_post( [ 'post_content' => $post_content ] );
+ $_POST['post_id'] = $post_id;
+
+ self::assertTrue( $subject->has_recaptcha() );
+ }
+}
diff --git a/.tests/php/integration/Stubs/CoBlocks_Form.php b/.tests/php/integration/Stubs/CoBlocks_Form.php
new file mode 100644
index 00000000..397d8454
--- /dev/null
+++ b/.tests/php/integration/Stubs/CoBlocks_Form.php
@@ -0,0 +1,20 @@
+recaptcha = [
+ 'siteKey' => 'some site key',
+ 'showForGuests' => 1,
+ 'showForUsers' => 1,
+ ];
+ $this->wp_discuz = Mockery::mock( 'WpdiscuzCore' );
+ $this->wp_discuz->options = $options;
+
+ FunctionMocker::replace(
+ 'function_exists',
+ static function ( $function_name ) {
+ return 'wpDiscuz' === $function_name;
+ }
+ );
+ FunctionMocker::replace( 'wpDiscuz', $this->wp_discuz );
+ }
+
/**
* Tear down test.
*
@@ -36,14 +72,29 @@ public function tearDown(): void {
public function test_init_hooks(): void {
$subject = new Comment();
- self::assertTrue( has_filter( 'wpdiscuz_recaptcha_site_key' ) );
self::assertSame( 11, has_action( 'wp_enqueue_scripts', [ $subject, 'enqueue_scripts' ] ) );
self::assertSame( 10, has_filter( 'wpdiscuz_form_render', [ $subject, 'add_hcaptcha' ] ) );
self::assertSame( 9, has_filter( 'preprocess_comment', [ $subject, 'verify' ] ) );
self::assertSame( 20, has_action( 'wp_head', [ $subject, 'print_inline_styles' ] ) );
+ }
+
+ /**
+ * Test init_hooks() without wpDiscuz.
+ *
+ * @return void
+ */
+ public function test_init_hooks_without_wpdiscuz(): void {
+ FunctionMocker::replace(
+ 'function_exists',
+ static function ( $function_name ) {
+ return 'wpDiscuz' !== $function_name;
+ }
+ );
+
+ $subject = new Comment();
- self::assertSame( '', apply_filters( 'wpdiscuz_recaptcha_site_key', 'some site key' ) );
+ self::assertFalse( has_action( 'wp_enqueue_scripts', [ $subject, 'enqueue_scripts' ] ) );
}
/**
@@ -112,19 +163,15 @@ public function test_verify(): void {
$_POST['action'] = 'wpdAddComment';
- $wp_discuz = Mockery::mock( 'WpdiscuzCore' );
-
- FunctionMocker::replace( 'wpDiscuz', $wp_discuz );
-
add_filter( 'wp_doing_ajax', '__return_true' );
- add_filter( 'preprocess_comment', [ $wp_discuz, 'validateRecaptcha' ] );
+ add_filter( 'preprocess_comment', [ $this->wp_discuz, 'validateRecaptcha' ] );
$this->prepare_hcaptcha_request_verify( $hcaptcha_response );
$subject = new Comment();
self::assertSame( $comment_data, $subject->verify( $comment_data ) );
- self::assertFalse( has_filter( 'preprocess_comment', [ $wp_discuz, 'validateRecaptcha' ] ) );
+ self::assertFalse( has_filter( 'preprocess_comment', [ $this->wp_discuz, 'validateRecaptcha' ] ) );
}
/**
@@ -167,12 +214,8 @@ public function test_verify_not_verified(): void {
$_POST['action'] = 'wpdAddComment';
- $wp_discuz = Mockery::mock( 'WpdiscuzCore' );
-
- FunctionMocker::replace( 'wpDiscuz', $wp_discuz );
-
add_filter( 'wp_doing_ajax', '__return_true' );
- add_filter( 'preprocess_comment', [ $wp_discuz, 'validateRecaptcha' ] );
+ add_filter( 'preprocess_comment', [ $this->wp_discuz, 'validateRecaptcha' ] );
$this->prepare_hcaptcha_request_verify( $hcaptcha_response, false );
@@ -194,7 +237,7 @@ static function ( $name ) use ( &$die_arr ) {
// phpcs:ignore WordPress.Security.NonceVerification.Missing
self::assertFalse( isset( $_POST['h-captcha-response'], $_POST['g-recaptcha-response'] ) );
self::assertSame( $expected, $die_arr );
- self::assertFalse( has_filter( 'preprocess_comment', [ $wp_discuz, 'validateRecaptcha' ] ) );
+ self::assertFalse( has_filter( 'preprocess_comment', [ $this->wp_discuz, 'validateRecaptcha' ] ) );
}
/**
diff --git a/.tests/php/integration/WPDiscuz/SubscribeTest.php b/.tests/php/integration/WPDiscuz/SubscribeTest.php
index f4c02705..b7e656a9 100644
--- a/.tests/php/integration/WPDiscuz/SubscribeTest.php
+++ b/.tests/php/integration/WPDiscuz/SubscribeTest.php
@@ -5,10 +5,15 @@
* @package HCaptcha\Tests
*/
+// phpcs:ignore Generic.Commenting.DocComment.MissingShort
+/** @noinspection PhpUndefinedClassInspection */
+
namespace HCaptcha\Tests\Integration\WPDiscuz;
use HCaptcha\Tests\Integration\HCaptchaWPTestCase;
use HCaptcha\WPDiscuz\Subscribe;
+use Mockery;
+use tad\FunctionMocker\FunctionMocker;
/**
* Test Subscribe class.
@@ -17,6 +22,40 @@
*/
class SubscribeTest extends HCaptchaWPTestCase {
+ /**
+ * The wpDiscuz core class mock.
+ *
+ * @var Mockery\MockInterface|WpdiscuzCore
+ * @noinspection PhpPrivateFieldCanBeLocalVariableInspection
+ */
+ private $wp_discuz;
+
+ /**
+ * Setup test.
+ *
+ * @return void
+ */
+ public function setUp(): void {
+ parent::setUp();
+
+ $options = Mockery::mock( 'WpdiscuzOptions' );
+ $options->recaptcha = [
+ 'siteKey' => 'some site key',
+ 'showForGuests' => 1,
+ 'showForUsers' => 1,
+ ];
+ $this->wp_discuz = Mockery::mock( 'WpdiscuzCore' );
+ $this->wp_discuz->options = $options;
+
+ FunctionMocker::replace(
+ 'function_exists',
+ static function ( $function_name ) {
+ return 'wpDiscuz' === $function_name;
+ }
+ );
+ FunctionMocker::replace( 'wpDiscuz', $this->wp_discuz );
+ }
+
/**
* Tear down test.
*
@@ -24,6 +63,8 @@ class SubscribeTest extends HCaptchaWPTestCase {
*/
public function tearDown(): void {
unset( $_POST['h-captcha-response'], $_POST['g-recaptcha-response'] );
+
+ parent::tearDown();
}
/**
@@ -34,15 +75,12 @@ public function tearDown(): void {
public function test_init_hooks(): void {
$subject = new Subscribe();
- self::assertTrue( has_filter( 'wpdiscuz_recaptcha_site_key' ) );
self::assertSame( 11, has_action( 'wp_enqueue_scripts', [ $subject, 'enqueue_scripts' ] ) );
self::assertSame( 10, has_action( 'wpdiscuz_after_subscription_form', [ $subject, 'add_hcaptcha' ] ) );
self::assertSame( 9, has_action( 'wp_ajax_wpdAddSubscription', [ $subject, 'verify' ] ) );
self::assertSame( 9, has_action( 'wp_ajax_nopriv_wpdAddSubscription', [ $subject, 'verify' ] ) );
self::assertSame( 20, has_action( 'wp_head', [ $subject, 'print_inline_styles' ] ) );
-
- self::assertSame( '', apply_filters( 'wpdiscuz_recaptcha_site_key', 'some site key' ) );
}
/**
diff --git a/.tests/php/integration/Wordfence/GeneralTest.php b/.tests/php/integration/Wordfence/GeneralTest.php
index c95e758b..2fe412aa 100644
--- a/.tests/php/integration/Wordfence/GeneralTest.php
+++ b/.tests/php/integration/Wordfence/GeneralTest.php
@@ -16,6 +16,7 @@
use HCaptcha\Wordfence\General;
use HCaptcha\WP\Login;
use ReflectionException;
+use tad\FunctionMocker\FunctionMocker;
/**
* Test General class.
@@ -130,4 +131,41 @@ public function test_remove_wp_login_hcaptcha_hooks(): void {
self::assertFalse( has_action( 'login_form', [ $wp_login, 'add_captcha' ] ) );
self::assertFalse( has_filter( 'wp_authenticate_user', [ $wp_login, 'check_signature' ] ) );
}
+
+ /**
+ * Test print_inline_styles().
+ *
+ * @return void
+ * @noinspection CssUnusedSymbol
+ */
+ public function test_print_inline_styles(): void {
+ FunctionMocker::replace(
+ 'defined',
+ static function ( $constant_name ) {
+ return 'SCRIPT_DEBUG' === $constant_name;
+ }
+ );
+
+ FunctionMocker::replace(
+ 'constant',
+ static function ( $name ) {
+ return 'SCRIPT_DEBUG' === $name;
+ }
+ );
+
+ $expected = << .h-captcha {
+ visibility: hidden !important;
+}
+CSS;
+ $expected = "\n";
+
+ $subject = new General();
+
+ ob_start();
+
+ $subject->print_inline_styles();
+
+ self::assertSame( $expected, ob_get_clean() );
+ }
}
diff --git a/.tests/php/integration/_bootstrap.php b/.tests/php/integration/_bootstrap.php
index 0dc46ced..5352f4a0 100644
--- a/.tests/php/integration/_bootstrap.php
+++ b/.tests/php/integration/_bootstrap.php
@@ -28,6 +28,7 @@
'constant',
'defined',
'filter_input',
+ 'function_exists',
'setcookie',
'time',
'uniqid',
diff --git a/.tests/php/integration/includes/RequestTest.php b/.tests/php/integration/includes/RequestTest.php
index 1790edd9..b44d8c29 100644
--- a/.tests/php/integration/includes/RequestTest.php
+++ b/.tests/php/integration/includes/RequestTest.php
@@ -158,6 +158,62 @@ public function test_hcap_get_error_message(): void {
);
}
+ /**
+ * Test hcap_check_site_config.
+ *
+ * @return void
+ */
+ public function test_hcap_check_site_config(): void {
+ add_filter(
+ 'pre_http_request',
+ static function ( $value, $parsed_args, $url ) use ( &$result ) {
+ if ( false !== strpos( $url, 'hcaptcha.com' ) ) {
+ return null === $result ? [] : [ 'body' => wp_json_encode( $result ) ];
+ }
+
+ return $value;
+ },
+ 10,
+ 3
+ );
+
+ // Cannot communicate.
+ $result = null;
+ $expected = [
+ 'error' => 'Cannot communicate with hCaptcha server.',
+ ];
+
+ self::assertSame( $expected, hcap_check_site_config() );
+
+ // Cannot decode.
+ $result = [];
+ $expected = [
+ 'error' => 'Cannot decode hCaptcha server response.',
+ ];
+
+ self::assertSame( $expected, hcap_check_site_config() );
+
+ // Error.
+ $error = 'some error';
+ $result = [
+ 'pass' => false,
+ 'error' => $error,
+ ];
+ $expected = [
+ 'error' => $error,
+ ];
+
+ self::assertSame( $expected, hcap_check_site_config() );
+
+ // Success.
+ $result = [
+ 'pass' => true,
+ ];
+ $expected = $result;
+
+ self::assertSame( $expected, hcap_check_site_config() );
+ }
+
/**
* Test hcaptcha_request_verify().
*/
@@ -213,9 +269,9 @@ public function test_hcaptcha_request_verify_not_verified_empty_body(): void {
}
/**
- * Test hcaptcha_verify_POST() with no argument.
+ * Test hcaptcha_verify_post() with no argument.
*/
- public function test_hcaptcha_verify_POST_default_success(): void {
+ public function test_hcaptcha_verify_post_default_success(): void {
$hcaptcha_response = 'some response';
$this->prepare_hcaptcha_request_verify( $hcaptcha_response );
@@ -224,16 +280,16 @@ public function test_hcaptcha_verify_POST_default_success(): void {
}
/**
- * Test hcaptcha_verify_POST() with no argument.
+ * Test hcaptcha_verify_post() with no argument.
*/
- public function test_hcaptcha_verify_POST_default_empty(): void {
+ public function test_hcaptcha_verify_post_default_empty(): void {
self::assertSame( 'Please complete the hCaptcha.', hcaptcha_verify_post() );
}
/**
- * Test hcaptcha_verify_POST().
+ * Test hcaptcha_verify_post().
*/
- public function test_hcaptcha_verify_POST(): void {
+ public function test_hcaptcha_verify_post(): void {
$nonce_field_name = 'some nonce field';
$nonce_action_name = 'some nonce action';
@@ -251,9 +307,9 @@ public function test_hcaptcha_verify_POST(): void {
}
/**
- * Test hcaptcha_verify_POST() not verified.
+ * Test hcaptcha_verify_post() not verified.
*/
- public function test_hcaptcha_verify_POST_not_verified(): void {
+ public function test_hcaptcha_verify_post_not_verified(): void {
$nonce_field_name = 'some nonce field';
$nonce_action_name = 'some nonce action';
@@ -263,9 +319,9 @@ public function test_hcaptcha_verify_POST_not_verified(): void {
}
/**
- * Test hcaptcha_verify_POST() not verified with empty POST.
+ * Test hcaptcha_verify_post() not verified with empty POST.
*/
- public function test_hcaptcha_verify_POST_not_verified_empty_POST(): void {
+ public function test_hcaptcha_verify_post_not_verified_empty_POST(): void {
$nonce_field_name = 'some nonce field';
$nonce_action_name = 'some nonce action';
@@ -275,9 +331,9 @@ public function test_hcaptcha_verify_POST_not_verified_empty_POST(): void {
}
/**
- * Test hcaptcha_verify_POST() not verified with logged-in user.
+ * Test hcaptcha_verify_post() not verified with logged-in user.
*/
- public function test_hcaptcha_verify_POST_not_verified_logged_in(): void {
+ public function test_hcaptcha_verify_post_not_verified_logged_in(): void {
$nonce_field_name = 'some nonce field';
$nonce_action_name = 'some nonce action';
diff --git a/.tests/php/unit/HCaptchaTestCase.php b/.tests/php/unit/HCaptchaTestCase.php
index 786c21dc..4aec706f 100644
--- a/.tests/php/unit/HCaptchaTestCase.php
+++ b/.tests/php/unit/HCaptchaTestCase.php
@@ -812,7 +812,10 @@ protected function get_test_integrations_form_fields(): array {
'type' => 'checkbox',
'options' =>
[
+ 'login' => 'Login Form',
+ 'lost_pass' => 'Lost Password Form',
'new_topic' => 'New Topic Form',
+ 'register' => 'Register Form',
'reply' => 'Reply Form',
],
],
diff --git a/.tests/php/unit/Helpers/PagesTest.php b/.tests/php/unit/Helpers/PagesTest.php
new file mode 100644
index 00000000..ec3d3f51
--- /dev/null
+++ b/.tests/php/unit/Helpers/PagesTest.php
@@ -0,0 +1,266 @@
+mock_filter_input();
+
+ // Not in admin.
+ FunctionMocker::replace( 'is_admin', false );
+ self::assertFalse( Pages::is_cf7_edit_page() );
+
+ // Not a CF7 page.
+ FunctionMocker::replace( 'is_admin', true );
+ self::assertFalse( Pages::is_cf7_edit_page() );
+
+ $_GET['page'] = 'some';
+ self::assertFalse( Pages::is_cf7_edit_page() );
+
+ $_GET['page'] = 'wpcf7-new';
+ self::assertTrue( Pages::is_cf7_edit_page() );
+
+ $_GET['page'] = 'wpcf7';
+ self::assertFalse( Pages::is_cf7_edit_page() );
+
+ $_GET['post'] = 'some';
+ self::assertTrue( Pages::is_cf7_edit_page() );
+ }
+
+ /**
+ * Test is_elementor_pro_edit_page().
+ *
+ * @return void
+ */
+ public function test_is_elementor_pro_edit_page(): void {
+ $this->mock_filter_input();
+
+ self::assertFalse( Pages::is_elementor_pro_edit_page() );
+
+ // Request 1.
+ $_GET['post'] = 'some';
+ $_SERVER['REQUEST_URI'] = '/wp-admin/post.php';
+ $_GET['action'] = 'elementor';
+
+ self::assertTrue( Pages::is_elementor_pro_edit_page() );
+
+ // Request 2.
+ unset( $_GET, $_POST, $_SERVER['REQUEST_URI'] );
+
+ $_GET['preview_id'] = 'some';
+ $_GET['preview_nonce'] = 'some';
+ $_GET['preview'] = 'true';
+
+ self::assertTrue( Pages::is_elementor_pro_edit_page() );
+
+ // Request 3.
+ unset( $_GET, $_POST );
+
+ $_POST['action'] = 'elementor_ajax';
+
+ self::assertTrue( Pages::is_elementor_pro_edit_page() );
+ }
+
+ /**
+ * Test is_gravity_edit_page().
+ *
+ * @return void
+ */
+ public function test_is_gravity_edit_page(): void {
+ $this->mock_filter_input();
+
+ self::assertFalse( Pages::is_gravity_edit_page() );
+
+ $_GET['id'] = 'some';
+ $_SERVER['REQUEST_URI'] = '/wp-admin/admin.php';
+ $_GET['page'] = 'gf_edit_forms';
+
+ self::assertTrue( Pages::is_gravity_edit_page() );
+ }
+
+ /**
+ * Test is_fluent_edit_page().
+ *
+ * @return void
+ */
+ public function test_is_fluent_edit_page(): void {
+ $this->mock_filter_input();
+
+ self::assertFalse( Pages::is_fluent_edit_page() );
+
+ $_SERVER['REQUEST_URI'] = '/wp-admin/admin.php';
+ $_GET['page'] = 'fluent_forms_settings';
+
+ self::assertTrue( Pages::is_fluent_edit_page() );
+ }
+
+ /**
+ * Test is_forminator_edit_page().
+ *
+ * @return void
+ */
+ public function test_is_forminator_edit_page(): void {
+ $this->mock_filter_input();
+
+ self::assertFalse( Pages::is_forminator_edit_page() );
+
+ $_GET['id'] = 'some';
+ $_SERVER['REQUEST_URI'] = '/wp-admin/admin.php';
+ $_GET['page'] = 'forminator-cform-wizard';
+
+ self::assertTrue( Pages::is_forminator_edit_page() );
+ }
+
+ /**
+ * Test is_formidable_edit_page().
+ *
+ * @return void
+ */
+ public function test_is_formidable_edit_page(): void {
+ $this->mock_filter_input();
+
+ self::assertFalse( Pages::is_formidable_forms_edit_page() );
+
+ // Request 1.
+ $_GET['frm_action'] = 'some';
+ $_GET['id'] = 'some';
+ $_SERVER['REQUEST_URI'] = '/wp-admin/admin.php';
+ $_GET['page'] = 'formidable';
+
+ self::assertTrue( Pages::is_formidable_forms_edit_page() );
+
+ // Request 2.
+ unset( $_GET, $_POST, $_SERVER['REQUEST_URI'] );
+
+ $_SERVER['REQUEST_URI'] = '/wp-admin/admin.php';
+ $_GET['page'] = 'formidable-styles';
+
+ self::assertTrue( Pages::is_formidable_forms_edit_page() );
+
+ // Request 3.
+ unset( $_GET, $_POST, $_SERVER['REQUEST_URI'] );
+
+ $_SERVER['REQUEST_URI'] = '/wp-admin/admin.php';
+ $_GET['page'] = 'formidable-settings';
+
+ self::assertTrue( Pages::is_formidable_forms_edit_page() );
+ }
+
+ /**
+ * Test is_is_ninja_edit_page().
+ *
+ * @return void
+ */
+ public function test_is_ninja_edit_page(): void {
+ $this->mock_filter_input();
+
+ self::assertFalse( Pages::is_ninja_edit_page() );
+
+ $_GET['form_id'] = 'some';
+ $_SERVER['REQUEST_URI'] = '/wp-admin/admin.php';
+ $_GET['page'] = 'ninja-forms';
+
+ self::assertTrue( Pages::is_ninja_edit_page() );
+ }
+
+ /**
+ * Test is_wpforms_edit_page().
+ *
+ * @return void
+ */
+ public function test_is_wpforms_edit_page(): void {
+ $this->mock_filter_input();
+
+ self::assertFalse( Pages::is_wpforms_edit_page() );
+
+ // Request 1.
+ $_GET['page'] = 'wpforms-settings';
+ $_GET['view'] = 'captcha';
+
+ FunctionMocker::replace( 'is_admin', true );
+
+ self::assertTrue( Pages::is_wpforms_edit_page() );
+
+ // Request 2.
+ unset( $_GET );
+
+ $_GET['wpforms_form_preview'] = 'some';
+
+ FunctionMocker::replace( 'is_admin', false );
+
+ self::assertTrue( Pages::is_wpforms_edit_page() );
+ }
+
+ /**
+ * Mock filter_input().
+ *
+ * @return void
+ */
+ private function mock_filter_input(): void {
+ FunctionMocker::replace(
+ '\HCaptcha\Helpers\Request::filter_input',
+ static function ( $type, $var_name ) {
+ // phpcs:disable WordPress.Security.NonceVerification.Recommended, WordPress.Security.NonceVerification.Missing
+ // phpcs:disable WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.ValidatedSanitizedInput.InputNotValidated
+ // phpcs:disable WordPress.Security.ValidatedSanitizedInput.MissingUnslash
+ switch ( $type ) {
+ case INPUT_GET:
+ return $_GET[ $var_name ] ?? '';
+ case INPUT_POST:
+ return $_POST[ $var_name ] ?? '';
+ case INPUT_SERVER:
+ return $_SERVER[ $var_name ] ?? '';
+ default:
+ return '';
+ }
+ // phpcs:enable WordPress.Security.NonceVerification.Recommended, WordPress.Security.NonceVerification.Missing
+ // phpcs:enable WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.ValidatedSanitizedInput.InputNotValidated
+ // phpcs:enable WordPress.Security.ValidatedSanitizedInput.MissingUnslash
+ }
+ );
+ }
+}
diff --git a/.tests/php/unit/Helpers/RequestTest.php b/.tests/php/unit/Helpers/RequestTest.php
index 3b6ca879..5409fafb 100644
--- a/.tests/php/unit/Helpers/RequestTest.php
+++ b/.tests/php/unit/Helpers/RequestTest.php
@@ -15,7 +15,6 @@
use HCaptcha\Helpers\Request;
use HCaptcha\Tests\Unit\HCaptchaTestCase;
use Mockery;
-use Mockery\Mock;
use tad\FunctionMocker\FunctionMocker;
use WP_Mock;
@@ -31,7 +30,7 @@ class RequestTest extends HCaptchaTestCase {
* Tear down test.
*/
public function tearDown(): void {
- unset( $_SERVER['REQUEST_URI'], $_GET['wc-ajax'], $_GET['rest_route'], $_SERVER['REQUEST_METHOD'] );
+ unset( $_GET, $_SERVER['REQUEST_URI'], $_SERVER['REQUEST_METHOD'] );
parent::tearDown();
}
@@ -148,8 +147,6 @@ public function test_is_wp_ajax(): void {
*/
public function test_is_rest(): void {
// No REQUEST_URI.
- unset( $_SERVER['REQUEST_URI'] );
-
self::assertFalse( Request::is_rest() );
// Case #1.
@@ -245,4 +242,46 @@ public function test_is_post(): void {
self::assertTrue( Request::is_post() );
}
+
+ /**
+ * Test filter_input().
+ *
+ * @return void
+ */
+ public function test_filter_input(): void {
+ WP_Mock::passthruFunction( 'wp_unslash' );
+ WP_Mock::passthruFunction( 'sanitize_text_field' );
+
+ // Test with GET.
+ $type = INPUT_GET;
+ $var_name = 'some_var';
+ $value = 'some_value';
+ $_GET[ $var_name ] = $value;
+
+ self::assertSame( '', Request::filter_input( $type, 'wrong_var_name' ) );
+ self::assertSame( $value, Request::filter_input( $type, $var_name ) );
+
+ // Test with POST.
+ unset( $_GET );
+ $type = INPUT_POST;
+ $_POST[ $var_name ] = $value;
+
+ self::assertSame( '', Request::filter_input( $type, 'wrong_var_name' ) );
+ self::assertSame( $value, Request::filter_input( $type, $var_name ) );
+
+ // Test with SERVER.
+ unset( $_POST );
+ $type = INPUT_SERVER;
+ $_SERVER[ $var_name ] = $value;
+
+ self::assertSame( '', Request::filter_input( $type, 'wrong_var_name' ) );
+ self::assertSame( $value, Request::filter_input( $type, $var_name ) );
+
+ // Test with a wrong input type.
+ unset( $_SERVER[ $var_name ] );
+ $type = 999;
+
+ self::assertSame( '', Request::filter_input( $type, 'wrong_var_name' ) );
+ self::assertSame( '', Request::filter_input( $type, $var_name ) );
+ }
}
diff --git a/.tests/php/unit/Settings/Abstracts/SettingsBaseTest.php b/.tests/php/unit/Settings/Abstracts/SettingsBaseTest.php
index be81b495..8b5eddd2 100644
--- a/.tests/php/unit/Settings/Abstracts/SettingsBaseTest.php
+++ b/.tests/php/unit/Settings/Abstracts/SettingsBaseTest.php
@@ -64,6 +64,7 @@ public function test_constructor( bool $is_tab, string $admin_mode, bool $is_net
'radio' => [ $subject, 'print_radio_field' ],
'select' => [ $subject, 'print_select_field' ],
'multiple' => [ $subject, 'print_multiple_select_field' ],
+ 'file' => [ $subject, 'print_file_field' ],
'table' => [ $subject, 'print_table_field' ],
'button' => [ $subject, 'print_button_field' ],
];
@@ -161,12 +162,33 @@ static function ( $name ) use ( $script_debug ) {
}
);
+ WP_Mock::userFunction( 'is_admin' )->once()->andReturn( true );
+
$subject->init();
$min_suffix = $script_debug ? '' : '.min';
self::assertSame( $min_suffix, $this->get_protected_property( $subject, 'min_suffix' ) );
}
+ /**
+ * Test init() when not in admin.
+ *
+ * @throws ReflectionException ReflectionException.
+ */
+ public function test_init_when_not_in_admin(): void {
+ $subject = Mockery::mock( SettingsBase::class )->makePartial();
+ $subject->shouldAllowMockingProtectedMethods();
+ $subject->shouldReceive( 'form_fields' )->once();
+ $subject->shouldReceive( 'init_settings' )->once();
+ $subject->shouldReceive( 'is_main_menu_page' )->never();
+ $subject->shouldReceive( 'is_tab_active' )->never();
+ $subject->shouldReceive( 'init_hooks' )->never();
+
+ WP_Mock::userFunction( 'is_admin' )->once()->andReturn( false );
+
+ $subject->init();
+ }
+
/**
* Data provider for test_init().
*
@@ -1196,6 +1218,19 @@ public function test_tabs_callback( bool $is_network_wide ): void {
self::assertSame( $expected, ob_get_clean() );
}
+ /**
+ * Test tabs_callback() when no tabs.
+ *
+ * @throws ReflectionException ReflectionException.
+ */
+ public function test_tabs_callback_when_no_tabs(): void {
+ $subject = Mockery::mock( SettingsBase::class )->makePartial();
+
+ ob_start();
+ $subject->tabs_callback();
+ self::assertSame( '', ob_get_clean() );
+ }
+
/**
* Data provider for test_tabs_callback().
*
@@ -1710,6 +1745,7 @@ public function test_field_callback( array $arguments, string $expected ): void
'radio' => [ $subject, 'print_radio_field' ],
'select' => [ $subject, 'print_select_field' ],
'multiple' => [ $subject, 'print_multiple_select_field' ],
+ 'file' => [ $subject, 'print_file_field' ],
'table' => [ $subject, 'print_table_field' ],
'button' => [ $subject, 'print_button_field' ],
];
@@ -1781,6 +1817,7 @@ public function dp_test_field_callback(): array {
$this->dp_radio_field_callback(),
$this->dp_select_field_callback(),
$this->dp_multiple_field_callback(),
+ $this->dp_file_field_callback(),
$this->dp_table_field_callback(),
$this->dp_button_field_callback()
);
@@ -2240,6 +2277,64 @@ private function dp_not_empty_multiple_field_callback(): array {
];
}
+ /**
+ * Data provider for file field.
+ *
+ * @return array
+ */
+ private function dp_file_field_callback(): array {
+ return [
+ 'File' => [
+ [
+ 'label' => 'file',
+ 'section' => 'some_section',
+ 'type' => 'file',
+ 'placeholder' => '',
+ 'helper' => '',
+ 'supplemental' => '',
+ 'default' => 1,
+ 'field_id' => 'some_id',
+ 'disabled' => false,
+ 'multiple' => false,
+ 'accept' => '',
+ ],
+ '',
+ ],
+ 'File disabled' => [
+ [
+ 'label' => 'file',
+ 'section' => 'some_section',
+ 'type' => 'file',
+ 'placeholder' => '',
+ 'helper' => '',
+ 'supplemental' => '',
+ 'default' => 1,
+ 'field_id' => 'some_id',
+ 'disabled' => true,
+ 'multiple' => false,
+ 'accept' => '',
+ ],
+ '',
+ ],
+ 'File multiple accept xml' => [
+ [
+ 'label' => 'file',
+ 'section' => 'some_section',
+ 'type' => 'file',
+ 'placeholder' => '',
+ 'helper' => '',
+ 'supplemental' => '',
+ 'default' => 1,
+ 'field_id' => 'some_id',
+ 'disabled' => false,
+ 'multiple' => true,
+ 'accept' => '.xml',
+ ],
+ '',
+ ],
+ ];
+ }
+
/**
* Data provider for table field.
*
@@ -2749,6 +2844,23 @@ public function dp_test_pre_update_option_filter(): array {
'_network_wide' => [],
],
],
+ [
+ [
+ 'some_file' => [
+ 'label' => 'some field',
+ 'section' => 'some_section',
+ 'type' => 'file',
+ 'placeholder' => '',
+ 'helper' => '',
+ 'supplemental' => '',
+ 'default' => [ '' ],
+ 'disabled' => false,
+ ],
+ ],
+ [ 'some_file' => 'a.xml' ],
+ [ 'some_file' => 'b.xml' ],
+ [ '_network_wide' => [] ],
+ ],
[
[],
[
diff --git a/.tests/php/unit/Settings/IntegrationsTest.php b/.tests/php/unit/Settings/IntegrationsTest.php
index f3e4be74..99dbd362 100644
--- a/.tests/php/unit/Settings/IntegrationsTest.php
+++ b/.tests/php/unit/Settings/IntegrationsTest.php
@@ -79,6 +79,7 @@ static function ( $function_name ) {
WP_Mock::userFunction( 'get_plugins' )->andReturn( $plugins );
WP_Mock::userFunction( 'wp_get_themes' )->andReturn( $themes );
+ WP_Mock::userFunction( 'is_admin' )->andReturn( true );
$method = 'init';
$subject->$method();
diff --git a/.tests/php/unit/Settings/SettingsTest.php b/.tests/php/unit/Settings/SettingsTest.php
index f88876e0..ecd21ad5 100644
--- a/.tests/php/unit/Settings/SettingsTest.php
+++ b/.tests/php/unit/Settings/SettingsTest.php
@@ -120,6 +120,25 @@ public function test_get_tabs(): void {
self::assertSame( $tabs, $subject->get_tabs() );
}
+ /**
+ * Test get_tab().
+ *
+ * @throws ReflectionException ReflectionException.
+ */
+ public function test_get_tab(): void {
+ $general = Mockery::mock( General::class )->makePartial();
+ $integrations = Mockery::mock( Integrations::class )->makePartial();
+
+ $subject = Mockery::mock( Settings::class )->makePartial();
+
+ self::assertNull( $subject->get_tab( Integrations::class ) );
+
+ $tabs = [ $general, $integrations ];
+ $this->set_protected_property( $subject, 'tabs', $tabs );
+
+ self::assertSame( $integrations, $subject->get_tab( Integrations::class ) );
+ }
+
/**
* Test get_active_tab_name().
*
diff --git a/.tests/php/unit/Settings/SystemInfoTest.php b/.tests/php/unit/Settings/SystemInfoTest.php
index 71726f06..b2f82b6c 100644
--- a/.tests/php/unit/Settings/SystemInfoTest.php
+++ b/.tests/php/unit/Settings/SystemInfoTest.php
@@ -338,7 +338,10 @@ public function test_get_system_info(): void {
Back In Stock Notifier:
Back In Stock Notifier Form: Off
bbPress:
+ Login Form: Off
+ Lost Password Form: Off
New Topic Form: On
+ Register Form: Off
Reply Form: On
Beaver Builder:
Contact Form: On
diff --git a/.wordpress-org/banner-1544x500.jpg b/.wordpress-org/banner-1544x500.jpg
new file mode 100644
index 00000000..a4434c54
Binary files /dev/null and b/.wordpress-org/banner-1544x500.jpg differ
diff --git a/.wordpress-org/banner-1544x500.png b/.wordpress-org/banner-1544x500.png
deleted file mode 100644
index b8ded94c..00000000
Binary files a/.wordpress-org/banner-1544x500.png and /dev/null differ
diff --git a/.wordpress-org/banner-772x250.jpg b/.wordpress-org/banner-772x250.jpg
index d7c85c2e..b8cec06d 100644
Binary files a/.wordpress-org/banner-772x250.jpg and b/.wordpress-org/banner-772x250.jpg differ
diff --git a/assets/css/settings-base.css b/assets/css/settings-base.css
index db090e9d..77415ed8 100644
--- a/assets/css/settings-base.css
+++ b/assets/css/settings-base.css
@@ -16,6 +16,9 @@ body.settings_page_hcaptcha {
}
.hcaptcha-settings-tabs {
+ position: sticky;
+ top: 0;
+ z-index: 2; /* +1 to .hcaptcha-header bar z-index, to overlap a bit */
display: flex;
flex-wrap: wrap;
justify-content: space-between;
@@ -45,10 +48,15 @@ body.settings_page_hcaptcha {
}
.hcaptcha-header-bar {
+ position: sticky;
+ top: 60px;
+ z-index: 1;
+ background: #f0f2f5;
display: flex;
justify-content: space-between;
align-items: center;
- margin: 0;
+ margin: 0 -20px;
+ padding: 0 20px;
}
#hcaptcha-options h2 {
diff --git a/assets/js/hcaptcha-mailpoet.js b/assets/js/hcaptcha-mailpoet.js
index c6d7a7ba..e76a1ee4 100644
--- a/assets/js/hcaptcha-mailpoet.js
+++ b/assets/js/hcaptcha-mailpoet.js
@@ -14,9 +14,10 @@
return;
}
- const urlParams = new URLSearchParams( data );
- const formId = urlParams.get( 'data[form_id]' );
- const $form = $( 'input[name="data[form_id]"][value=' + formId + ']' ).parent( 'form' );
+ // eslint-disable-next-line @wordpress/no-global-active-element
+ const eventTarget = options.context || document.activeElement;
+ const $form = $( eventTarget.closest( 'form' ) );
+
let response = $form.find( '[name="h-captcha-response"]' ).val();
response = response ? response : '';
let id = $form.find( '[name="hcaptcha-widget-id"]' ).val();
diff --git a/assets/js/integrations.js b/assets/js/integrations.js
index b2b63c22..50136931 100644
--- a/assets/js/integrations.js
+++ b/assets/js/integrations.js
@@ -24,12 +24,24 @@
* @param {jQuery} $ The jQuery instance.
*/
const integrations = function( $ ) {
+ const adminBar = document.querySelector( '#wpadminbar' );
+ const tabs = document.querySelector( '.hcaptcha-settings-tabs' );
+ const headerBar = document.querySelector( '.hcaptcha-header-bar' );
const msgSelector = '#hcaptcha-message';
let $message = $( msgSelector );
const $wpWrap = $( '#wpwrap' );
const $adminmenuwrap = $( '#adminmenuwrap' );
const $search = $( '#hcaptcha-integrations-search' );
+ function getStickyHeight() {
+ const isAbsolute = adminBar ? window.getComputedStyle( adminBar ).position === 'absolute' : true;
+ const adminBarHeight = ( adminBar && ! isAbsolute ) ? adminBar.offsetHeight : 0;
+ const tabsHeight = tabs ? tabs.offsetHeight : 0;
+ const headerBarHeight = headerBar ? headerBar.offsetHeight : 0;
+
+ return adminBarHeight + tabsHeight + headerBarHeight;
+ }
+
function clearMessage() {
$message.remove();
$( '' ).insertAfter( '.hcaptcha-header-bar' );
@@ -222,7 +234,6 @@ const integrations = function( $ ) {
}
const $table = $( '.form-table' ).eq( activate ? 0 : 1 );
- const top = $wpWrap.position().top;
swapThemes( activate, entity, newTheme );
insertIntoTable( $table, alt, $tr );
@@ -231,7 +242,7 @@ const integrations = function( $ ) {
$( 'html, body' ).animate(
{
- scrollTop: $tr.offset().top - top - $message.outerHeight(),
+ scrollTop: $tr.offset().top - getStickyHeight(),
},
1000
);
diff --git a/assets/js/settings-base.js b/assets/js/settings-base.js
index 97849a98..ecb1b7a2 100644
--- a/assets/js/settings-base.js
+++ b/assets/js/settings-base.js
@@ -9,8 +9,25 @@ const settingsBase = function( $ ) {
const h2Selector = '.hcaptcha-header h2';
const msgSelector = '#hcaptcha-message';
- // Move WP notices to the message area.
- $( h2Selector ).siblings().appendTo( msgSelector );
+ function setHeaderBarTop() {
+ const adminBar = document.querySelector( '#wpadminbar' );
+ const tabs = document.querySelector( '.hcaptcha-settings-tabs' );
+ const headerBar = document.querySelector( '.hcaptcha-header-bar' );
+
+ const isAbsolute = adminBar ? window.getComputedStyle( adminBar ).position === 'absolute' : true;
+ const adminBarHeight = ( adminBar && ! isAbsolute ) ? adminBar.offsetHeight : 0;
+ const tabsHeight = tabs ? tabs.offsetHeight : 0;
+ // The -1 to put header bar a bit under tabs. It is a precaution when heights are in fractional pixels.
+ const totalHeight = adminBarHeight + tabsHeight - 1;
+
+ if ( tabs ) {
+ tabs.style.top = `${ adminBarHeight }px`;
+ }
+
+ if ( headerBar ) {
+ headerBar.style.top = `${ totalHeight }px`;
+ }
+ }
/**
* Highlight element if hash is present in the URL.
@@ -42,6 +59,15 @@ const settingsBase = function( $ ) {
}
}
+ // Move WP notices to the message area.
+ $( h2Selector ).siblings().appendTo( msgSelector );
+
+ window.addEventListener( 'resize', function() {
+ setHeaderBarTop();
+ } );
+
+ setHeaderBarTop();
+
highLight();
};
diff --git a/changelog.txt b/changelog.txt
index b4f3e57a..63332cac 100644
--- a/changelog.txt
+++ b/changelog.txt
@@ -1,3 +1,27 @@
+= 4.5.0 =
+* Added support for Jetpack forms in block theme templates.
+* Added support for bbPress Login, Register and Lost Password forms.
+* Added the second argument $atts to the 'hcap_hcaptcha_content' filter.
+* Added support for MailPoet forms at any placement.
+* Added the ability to have multiple MailPoet forms on the same page.
+* Improved UX of the Integrations page.
+* Fixed error messaging when there are several Jetpack forms on the same page.
+* Fixed unconditional forcing hCaptcha in Jetpack forms.
+* Fixed appearance of Beaver Builder editor with "Turn Off When Logged In" setting.
+* Fixed appearance of Contact Form 7 editor with "Turn Off When Logged In" setting.
+* Fixed appearance of Essential Addons editor with "Turn Off When Logged In" setting.
+* Fixed appearance of Gravity Forms editor with "Turn Off When Logged In" setting.
+* Fixed appearance of Fluent Forms editor with "Turn Off When Logged In" setting.
+* Fixed appearance of Forminator editor with "Turn Off When Logged In" setting.
+* Fixed appearance of Formidable Forms with "Turn Off When Logged In" setting.
+* Fixed appearance of Ninja Forms editor with "Turn Off When Logged In" setting.
+* Fixed appearance of WPForms editor with "Turn Off When Logged In" setting.
+* Fixed fatal error on Gravity Forms Entries page.
+* Fixed Elementor preview.
+* Fixed Ninja Forms preview.
+* Fixed hCaptcha nonce error on MailPoet admin pages.
+* Fixed frontend error when recaptcha was activated in wpDiscuz.
+
= 4.4.0 =
* Added compatibility with Contact Form 7 Stripe integration.
* Added compatibility with WPS Hide Login plugin.
diff --git a/composer.json b/composer.json
index 3f8ac607..d8f657b5 100644
--- a/composer.json
+++ b/composer.json
@@ -41,7 +41,7 @@
"codeception/module-webdriver": "^1.4.1",
"lucatume/function-mocker": "dev-master",
"lucatume/wp-browser": "^3.6.5",
- "squizlabs/php_codesniffer": "^3.10.1",
+ "squizlabs/php_codesniffer": "^3.10.2",
"phpcompatibility/php-compatibility": "^9.3.5",
"phpcompatibility/phpcompatibility-wp": "^2.1.5",
"wp-coding-standards/wpcs": "^3.1.0"
diff --git a/hcaptcha.php b/hcaptcha.php
index f94b2084..77576458 100644
--- a/hcaptcha.php
+++ b/hcaptcha.php
@@ -10,7 +10,7 @@
* Plugin Name: hCaptcha for WP
* Plugin URI: https://www.hcaptcha.com/
* Description: hCaptcha keeps out bots and spam while putting privacy first. It is a drop-in replacement for reCAPTCHA.
- * Version: 4.4.0
+ * Version: 4.5.0
* Requires at least: 5.3
* Requires PHP: 7.2
* Author: hCaptcha
@@ -39,7 +39,7 @@
/**
* Plugin version.
*/
-const HCAPTCHA_VERSION = '4.4.0';
+const HCAPTCHA_VERSION = '4.5.0';
/**
* Path to the plugin dir.
diff --git a/readme.txt b/readme.txt
index 8a3392ff..5c51905c 100644
--- a/readme.txt
+++ b/readme.txt
@@ -4,7 +4,7 @@ Tags: captcha, hcaptcha, antispam, abuse, protect form
Requires at least: 5.3
Tested up to: 6.6
Requires PHP: 7.2
-Stable tag: 4.4.0
+Stable tag: 4.5.0
License: GPLv2 or later
License URI: https://www.gnu.org/licenses/gpl-2.0.html
@@ -16,7 +16,7 @@ Enables hCaptcha integration with WordPress and popular plugins.
Need to keep out bots? hCaptcha protects privacy while offering better protection against spam and abuse. Help build a better web.
-[The hCaptcha for WP many advantages over similar captcha plugins](https://kagg.eu/why-choose-hcaptcha-for-wp/).
+hCaptcha for WP [makes security easy](https://www.hcaptcha.com/integration-hcaptcha-for-wp) with broad integration support, detailed analytics, and strong protection. Start protecting logins, forms, and more in minutes.
== How hCaptcha Works ==
@@ -36,7 +36,7 @@ To use this plugin, install it and enter your sitekey and secret in the Settings
4. Contact Form 7 with hCaptcha.
5. Contact Form 7 live form in the admin editor.
6. Elementor Pro Form.
-7. Elementor Pro From in admin editor.
+7. Elementor Pro Form in admin editor.
8. General settings page.
9. Integrations settings page.
10. Activating plugin from the Integration settings page.
@@ -196,7 +196,7 @@ Back In Stock Notifier
BBPress
`$source: 'bbpress/bbpress.php'`
-`$form_id: 'new_topic' or 'reply'`
+`$form_id: 'new_topic', 'reply', 'login', 'register' or 'lost_password'`
Beaver Builder
`$source: 'bb-plugin/fl-builder.php'`
@@ -232,7 +232,7 @@ Elementor Pro
Jetpack
`$source: 'jetpack/jetpack.php'`
-`$form_id: 'contact'`
+`$form_id: 'contact_$form_hash'`
Kadence Form
`$source: 'kadence-blocks/kadence-blocks.php'`
@@ -394,7 +394,7 @@ add_filter( 'hcap_language', 'my_hcap_language' );
= How to whitelist certain IPs =
-You can use the following filter:
+You can use the following filter. It should be added to your plugin's (or mu-plugin's) main file. This filter won't work being added to a theme's functions.php file.
`
/**
@@ -494,7 +494,7 @@ If this feature is enabled, anonymized statistics on your plugin configuration,
* Asgaros Forum New Topic and Reply Form
* Avada Form
* Back In Stock Notifier Form
-* bbPress New Topic and Reply Forms
+* bbPress New Topic, Reply, Login, Register and Lost Password Forms
* Beaver Builder Contact and Login Forms
* BuddyPress — Create Group and Registration Forms
* Classified Listing Contact, Login, Lost Password, and Listing Register Forms
@@ -565,6 +565,30 @@ Instructions for popular native integrations are below:
== Changelog ==
+= 4.5.0 =
+* Added support for Jetpack forms in block theme templates.
+* Added support for bbPress Login, Register and Lost Password forms.
+* Added the second argument $atts to the 'hcap_hcaptcha_content' filter.
+* Added support for MailPoet forms at any placement.
+* Added the ability to have multiple MailPoet forms on the same page.
+* Improved UX of the Integrations page.
+* Fixed error messaging when there are several Jetpack forms on the same page.
+* Fixed unconditional forcing hCaptcha in Jetpack forms.
+* Fixed appearance of Beaver Builder editor with "Turn Off When Logged In" setting.
+* Fixed appearance of Contact Form 7 editor with "Turn Off When Logged In" setting.
+* Fixed appearance of Essential Addons editor with "Turn Off When Logged In" setting.
+* Fixed appearance of Gravity Forms editor with "Turn Off When Logged In" setting.
+* Fixed appearance of Fluent Forms editor with "Turn Off When Logged In" setting.
+* Fixed appearance of Forminator editor with "Turn Off When Logged In" setting.
+* Fixed appearance of Formidable Forms with "Turn Off When Logged In" setting.
+* Fixed appearance of Ninja Forms editor with "Turn Off When Logged In" setting.
+* Fixed appearance of WPForms editor with "Turn Off When Logged In" setting.
+* Fixed fatal error on Gravity Forms Entries page.
+* Fixed Elementor preview.
+* Fixed Ninja Forms preview.
+* Fixed hCaptcha nonce error on MailPoet admin pages.
+* Fixed frontend error when recaptcha was activated in wpDiscuz.
+
= 4.4.0 =
* Added compatibility with Contact Form 7 Stripe integration.
* Added compatibility with WPS Hide Login plugin.
diff --git a/src/php/BBPress/Login.php b/src/php/BBPress/Login.php
new file mode 100644
index 00000000..bf780b9e
--- /dev/null
+++ b/src/php/BBPress/Login.php
@@ -0,0 +1,73 @@
+is_login_limit_exceeded() ) {
+ return $output;
+ }
+
+ $hcaptcha = '';
+
+ // Check the login status, because class is always loading when bbPress is active.
+ if ( hcaptcha()->settings()->is( 'bbp_status', 'login' ) ) {
+ ob_start();
+
+ $this->add_captcha();
+ $hcaptcha = (string) ob_get_clean();
+ }
+
+ ob_start();
+
+ /**
+ * Display hCaptcha signature.
+ */
+ do_action( 'hcap_signature' );
+
+ $signatures = (string) ob_get_clean();
+
+ $pattern = '/(