From 4e390814a3ff9866181b6175983d79abdd5a5029 Mon Sep 17 00:00:00 2001 From: kagg-design Date: Thu, 1 Aug 2024 23:17:41 +0300 Subject: [PATCH 01/59] Fix frontend error when recaptcha was activated in wpDiscuz. --- .../php/integration/WPDiscuz/CommentTest.php | 46 +++++++++++++------ .../integration/WPDiscuz/SubscribeTest.php | 32 +++++++++++-- readme.txt | 3 ++ src/php/WPDiscuz/Base.php | 17 ++++--- 4 files changed, 74 insertions(+), 24 deletions(-) diff --git a/.tests/php/integration/WPDiscuz/CommentTest.php b/.tests/php/integration/WPDiscuz/CommentTest.php index f3cb9ef5..73410e11 100644 --- a/.tests/php/integration/WPDiscuz/CommentTest.php +++ b/.tests/php/integration/WPDiscuz/CommentTest.php @@ -19,6 +19,33 @@ */ class CommentTest extends HCaptchaWPTestCase { + /** + * wpDiscuz core class mock. + * + * @var Mockery\MockInterface|WpdiscuzCore + */ + 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( 'wpDiscuz', $this->wp_discuz ); + } + /** * Tear down test. * @@ -36,14 +63,11 @@ 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' ] ) ); - - self::assertSame( '', apply_filters( 'wpdiscuz_recaptcha_site_key', 'some site key' ) ); } /** @@ -112,19 +136,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 +187,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 +210,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..62969e14 100644 --- a/.tests/php/integration/WPDiscuz/SubscribeTest.php +++ b/.tests/php/integration/WPDiscuz/SubscribeTest.php @@ -9,6 +9,8 @@ use HCaptcha\Tests\Integration\HCaptchaWPTestCase; use HCaptcha\WPDiscuz\Subscribe; +use Mockery; +use tad\FunctionMocker\FunctionMocker; /** * Test Subscribe class. @@ -17,6 +19,33 @@ */ class SubscribeTest extends HCaptchaWPTestCase { + /** + * wpDiscuz core class mock. + * + * @var Mockery\MockInterface|WpdiscuzCore + */ + 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( 'wpDiscuz', $this->wp_discuz ); + } + /** * Tear down test. * @@ -34,15 +63,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/readme.txt b/readme.txt index 8a3392ff..021b1545 100644 --- a/readme.txt +++ b/readme.txt @@ -565,6 +565,9 @@ Instructions for popular native integrations are below: == Changelog == += 4.5.0 = +* 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/WPDiscuz/Base.php b/src/php/WPDiscuz/Base.php index c97b1cc7..43b5f647 100644 --- a/src/php/WPDiscuz/Base.php +++ b/src/php/WPDiscuz/Base.php @@ -23,16 +23,21 @@ public function __construct() { * Add hooks. * * @return void + * @noinspection PhpUndefinedFunctionInspection */ protected function init_hooks(): void { - add_filter( - 'wpdiscuz_recaptcha_site_key', - static function () { - // Block output of reCaptcha by wpDiscuz. - return ''; - } + $wpd_recaptcha = wpDiscuz()->options->recaptcha; + $wpd_recaptcha = array_merge( + $wpd_recaptcha, + [ + 'siteKey' => '', + 'showForGuests' => 0, + 'showForUsers' => 0, + ] ); + wpDiscuz()->options->recaptcha = $wpd_recaptcha; + add_action( 'wp_enqueue_scripts', [ $this, 'enqueue_scripts' ], 11 ); } From 0882b27c9a663fcb9918d616383f34c2fb067814 Mon Sep 17 00:00:00 2001 From: kagg-design Date: Mon, 5 Aug 2024 11:32:38 +0300 Subject: [PATCH 02/59] Do not show tab links if there is one tab only. --- src/php/Settings/Abstracts/SettingsBase.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/php/Settings/Abstracts/SettingsBase.php b/src/php/Settings/Abstracts/SettingsBase.php index 45079815..115c7d55 100644 --- a/src/php/Settings/Abstracts/SettingsBase.php +++ b/src/php/Settings/Abstracts/SettingsBase.php @@ -707,6 +707,10 @@ public function setup_tabs_section(): void { * Show tabs. */ public function tabs_callback(): void { + if ( count( $this->tabs ) < 2 ) { + return; + } + ?>
From 757f59e697e3b64e172e508f89a6cb0471d4af66 Mon Sep 17 00:00:00 2001 From: kagg-design Date: Mon, 5 Aug 2024 11:33:54 +0300 Subject: [PATCH 03/59] Add file field. --- src/php/Settings/Abstracts/SettingsBase.php | 24 +++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/php/Settings/Abstracts/SettingsBase.php b/src/php/Settings/Abstracts/SettingsBase.php index 115c7d55..f6c26afa 100644 --- a/src/php/Settings/Abstracts/SettingsBase.php +++ b/src/php/Settings/Abstracts/SettingsBase.php @@ -232,6 +232,7 @@ public function __construct( $tabs = [], $args = [] ) { 'radio' => [ $this, 'print_radio_field' ], 'select' => [ $this, 'print_select_field' ], 'multiple' => [ $this, 'print_multiple_select_field' ], + 'file' => [ $this, 'print_file_field' ], 'table' => [ $this, 'print_table_field' ], 'button' => [ $this, 'print_button_field' ], ]; @@ -1238,6 +1239,29 @@ protected function print_multiple_select_field( array $arguments ): void { ); } + /** + * Print file field. + * + * @param array $arguments Field arguments. + * + * @return void + * @noinspection HtmlUnknownAttribute + */ + protected function print_file_field( array $arguments ): void { + $multiple = (bool) ( $arguments['multiple'] ?? '' ); + $accept = $arguments['accept'] ?? ''; + + printf( + '', + disabled( $arguments['disabled'], true, false ), + esc_html( $this->option_name() ), + esc_attr( $arguments['field_id'] ), + esc_attr( $multiple ? '[]' : '' ), + esc_attr( $multiple ? 'mu1ltiple' : '' ), + $accept ? 'accept="' . esc_attr( $accept ) . '"' : '' + ); + } + /** * Print table field. * From 13accd89b289370701305e6b36fa6a4b67039d9a Mon Sep 17 00:00:00 2001 From: kagg-design Date: Mon, 5 Aug 2024 11:36:52 +0300 Subject: [PATCH 04/59] Do not save file field. --- src/php/Settings/Abstracts/SettingsBase.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/php/Settings/Abstracts/SettingsBase.php b/src/php/Settings/Abstracts/SettingsBase.php index f6c26afa..0762660c 100644 --- a/src/php/Settings/Abstracts/SettingsBase.php +++ b/src/php/Settings/Abstracts/SettingsBase.php @@ -1488,6 +1488,11 @@ public function pre_update_option_filter( $value, $old_value ) { $old_value = is_array( $old_value ) ? $old_value : []; foreach ( $this->form_fields() as $key => $form_field ) { + if ( 'file' === $form_field['type'] ) { + unset( $value[ $key ], $old_value[ $key ] ); + continue; + } + if ( 'checkbox' !== $form_field['type'] || isset( $value[ $key ] ) ) { continue; } From 8d36ebce1b30d06e415a61f4d71cdb82f4fccf26 Mon Sep 17 00:00:00 2001 From: kagg-design Date: Mon, 5 Aug 2024 11:47:46 +0300 Subject: [PATCH 05/59] Introduce savable form fields concept. --- src/php/Settings/Abstracts/SettingsBase.php | 16 ++++++++++++++++ src/php/Settings/PluginSettingsBase.php | 2 +- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/src/php/Settings/Abstracts/SettingsBase.php b/src/php/Settings/Abstracts/SettingsBase.php index 0762660c..23211b90 100644 --- a/src/php/Settings/Abstracts/SettingsBase.php +++ b/src/php/Settings/Abstracts/SettingsBase.php @@ -1652,4 +1652,20 @@ protected function print_header(): void {
form_fields, + static function ( $field ) use ( $not_savable_form_fields ) { + return ! in_array( $field['type'], $not_savable_form_fields, true ); + } + ); + } } diff --git a/src/php/Settings/PluginSettingsBase.php b/src/php/Settings/PluginSettingsBase.php index 3a4df3c3..5add05a6 100644 --- a/src/php/Settings/PluginSettingsBase.php +++ b/src/php/Settings/PluginSettingsBase.php @@ -196,7 +196,7 @@ class="hcaptcha-section_title() ); ?>" do_settings_sections( $this->option_page() ); // Sections with options. settings_fields( $this->option_group() ); // Hidden protection fields. - if ( ! empty( $this->form_fields ) ) { + if ( ! empty( $this->get_savable_form_fields() ) ) { $this->submit_button(); } ?> From 4cd56e95fa395aff8ed1dbbf5418f9e70dd05d61 Mon Sep 17 00:00:00 2001 From: kagg-design Date: Mon, 5 Aug 2024 12:23:00 +0300 Subject: [PATCH 06/59] Fix typo. --- src/php/Settings/Abstracts/SettingsBase.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/php/Settings/Abstracts/SettingsBase.php b/src/php/Settings/Abstracts/SettingsBase.php index 23211b90..fa4cd353 100644 --- a/src/php/Settings/Abstracts/SettingsBase.php +++ b/src/php/Settings/Abstracts/SettingsBase.php @@ -1257,7 +1257,7 @@ protected function print_file_field( array $arguments ): void { esc_html( $this->option_name() ), esc_attr( $arguments['field_id'] ), esc_attr( $multiple ? '[]' : '' ), - esc_attr( $multiple ? 'mu1ltiple' : '' ), + esc_attr( $multiple ? 'multiple' : '' ), $accept ? 'accept="' . esc_attr( $accept ) . '"' : '' ); } From a20156102f565a2bc680a024e2b06a3ba23992b5 Mon Sep 17 00:00:00 2001 From: kagg-design Date: Tue, 6 Aug 2024 10:38:41 +0300 Subject: [PATCH 07/59] Fix tests. --- .tests/php/unit/Settings/Abstracts/SettingsBaseTest.php | 1 + src/php/Settings/Abstracts/SettingsBase.php | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.tests/php/unit/Settings/Abstracts/SettingsBaseTest.php b/.tests/php/unit/Settings/Abstracts/SettingsBaseTest.php index be81b495..326995ef 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' ], ]; diff --git a/src/php/Settings/Abstracts/SettingsBase.php b/src/php/Settings/Abstracts/SettingsBase.php index fa4cd353..1a67043f 100644 --- a/src/php/Settings/Abstracts/SettingsBase.php +++ b/src/php/Settings/Abstracts/SettingsBase.php @@ -708,7 +708,7 @@ public function setup_tabs_section(): void { * Show tabs. */ public function tabs_callback(): void { - if ( count( $this->tabs ) < 2 ) { + if ( ! count( $this->tabs ) ) { return; } @@ -1664,7 +1664,7 @@ protected function get_savable_form_fields(): array { return array_filter( $this->form_fields, static function ( $field ) use ( $not_savable_form_fields ) { - return ! in_array( $field['type'], $not_savable_form_fields, true ); + return ! in_array( $field['type'] ?? '', $not_savable_form_fields, true ); } ); } From 65ca3284f61f2e77d81481125469bf1080d00a7b Mon Sep 17 00:00:00 2001 From: kagg-design Date: Tue, 6 Aug 2024 10:46:11 +0300 Subject: [PATCH 08/59] Fix tests. --- .tests/php/integration/WPDiscuz/CommentTest.php | 2 +- .tests/php/integration/WPDiscuz/SubscribeTest.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.tests/php/integration/WPDiscuz/CommentTest.php b/.tests/php/integration/WPDiscuz/CommentTest.php index 73410e11..645ceb55 100644 --- a/.tests/php/integration/WPDiscuz/CommentTest.php +++ b/.tests/php/integration/WPDiscuz/CommentTest.php @@ -20,7 +20,7 @@ class CommentTest extends HCaptchaWPTestCase { /** - * wpDiscuz core class mock. + * The wpDiscuz core class mock. * * @var Mockery\MockInterface|WpdiscuzCore */ diff --git a/.tests/php/integration/WPDiscuz/SubscribeTest.php b/.tests/php/integration/WPDiscuz/SubscribeTest.php index 62969e14..c069b2aa 100644 --- a/.tests/php/integration/WPDiscuz/SubscribeTest.php +++ b/.tests/php/integration/WPDiscuz/SubscribeTest.php @@ -20,7 +20,7 @@ class SubscribeTest extends HCaptchaWPTestCase { /** - * wpDiscuz core class mock. + * The wpDiscuz core class mock. * * @var Mockery\MockInterface|WpdiscuzCore */ From d534254f0ee9ca345eba25e052036e509f9315ad Mon Sep 17 00:00:00 2001 From: kagg-design Date: Tue, 6 Aug 2024 11:23:40 +0300 Subject: [PATCH 09/59] Fix tests. --- src/php/WPDiscuz/Base.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/php/WPDiscuz/Base.php b/src/php/WPDiscuz/Base.php index 43b5f647..992d5fc3 100644 --- a/src/php/WPDiscuz/Base.php +++ b/src/php/WPDiscuz/Base.php @@ -26,6 +26,10 @@ public function __construct() { * @noinspection PhpUndefinedFunctionInspection */ protected function init_hooks(): void { + if ( ! function_exists( 'wpDiscuz' ) ) { + return; + } + $wpd_recaptcha = wpDiscuz()->options->recaptcha; $wpd_recaptcha = array_merge( $wpd_recaptcha, From 01d3e15722fd9fb9095d22d57f633444d625d3bd Mon Sep 17 00:00:00 2001 From: kagg-design Date: Tue, 6 Aug 2024 12:30:09 +0300 Subject: [PATCH 10/59] Raise unit tests coverage to 100%. --- .../Settings/Abstracts/SettingsBaseTest.php | 90 +++++++++++++++++++ .tests/php/unit/Settings/SettingsTest.php | 19 ++++ src/php/Settings/Abstracts/SettingsBase.php | 2 +- src/php/Settings/Settings.php | 4 +- 4 files changed, 111 insertions(+), 4 deletions(-) diff --git a/.tests/php/unit/Settings/Abstracts/SettingsBaseTest.php b/.tests/php/unit/Settings/Abstracts/SettingsBaseTest.php index 326995ef..88fab1f7 100644 --- a/.tests/php/unit/Settings/Abstracts/SettingsBaseTest.php +++ b/.tests/php/unit/Settings/Abstracts/SettingsBaseTest.php @@ -1197,6 +1197,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(). * @@ -1711,6 +1724,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' ], ]; @@ -1782,6 +1796,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() ); @@ -2241,6 +2256,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. * @@ -2750,6 +2823,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/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/src/php/Settings/Abstracts/SettingsBase.php b/src/php/Settings/Abstracts/SettingsBase.php index 1a67043f..5f2435fc 100644 --- a/src/php/Settings/Abstracts/SettingsBase.php +++ b/src/php/Settings/Abstracts/SettingsBase.php @@ -708,7 +708,7 @@ public function setup_tabs_section(): void { * Show tabs. */ public function tabs_callback(): void { - if ( ! count( $this->tabs ) ) { + if ( ! count( $this->tabs ?? [] ) ) { return; } diff --git a/src/php/Settings/Settings.php b/src/php/Settings/Settings.php index bd7dd7c8..3cb47228 100644 --- a/src/php/Settings/Settings.php +++ b/src/php/Settings/Settings.php @@ -105,9 +105,7 @@ public function get_tabs(): array { * @return PluginSettingsBase|null */ public function get_tab( string $classname ): ?PluginSettingsBase { - $tabs = hcaptcha()->settings()->get_tabs(); - - foreach ( $tabs as $tab ) { + foreach ( $this->tabs as $tab ) { if ( is_a( $tab, $classname ) ) { return $tab; } From 40d899de2f44cff61192b77604ac8cd16d6271d6 Mon Sep 17 00:00:00 2001 From: kagg-design Date: Thu, 8 Aug 2024 20:24:17 +0300 Subject: [PATCH 11/59] Raise unit tests coverage to 100%. --- .../php/unit/Settings/Abstracts/SettingsBaseTest.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.tests/php/unit/Settings/Abstracts/SettingsBaseTest.php b/.tests/php/unit/Settings/Abstracts/SettingsBaseTest.php index 88fab1f7..7d41be0a 100644 --- a/.tests/php/unit/Settings/Abstracts/SettingsBaseTest.php +++ b/.tests/php/unit/Settings/Abstracts/SettingsBaseTest.php @@ -2263,7 +2263,7 @@ private function dp_not_empty_multiple_field_callback(): array { */ private function dp_file_field_callback(): array { return [ - 'File' => [ + 'File' => [ [ 'label' => 'file', 'section' => 'some_section', @@ -2277,9 +2277,9 @@ private function dp_file_field_callback(): array { 'multiple' => false, 'accept' => '', ], - '' + '', ], - 'File disabled' => [ + 'File disabled' => [ [ 'label' => 'file', 'section' => 'some_section', @@ -2293,9 +2293,9 @@ private function dp_file_field_callback(): array { 'multiple' => false, 'accept' => '', ], - '' + '', ], - 'File multiple accept xml' => [ + 'File multiple accept xml' => [ [ 'label' => 'file', 'section' => 'some_section', @@ -2309,7 +2309,7 @@ private function dp_file_field_callback(): array { 'multiple' => true, 'accept' => '.xml', ], - '' + '', ], ]; } From c6e53e031949f6489f178aec0c8bc529d858b823 Mon Sep 17 00:00:00 2001 From: kagg-design Date: Thu, 8 Aug 2024 20:28:14 +0300 Subject: [PATCH 12/59] Remove codeception.yml from the release. --- .distignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.distignore b/.distignore index 7566f3a7..d2a37a76 100644 --- a/.distignore +++ b/.distignore @@ -16,7 +16,7 @@ # Files /.DS_Store /.babelrc -/.codeception.yml +/codeception.yml /.distignore /.editorconfig /.eslintrc.json From 7f27db5c1554d3961bdc4a9b757e85c2898f54c9 Mon Sep 17 00:00:00 2001 From: kagg-design Date: Thu, 8 Aug 2024 20:32:17 +0300 Subject: [PATCH 13/59] Remove codeception.yml from the release. --- .tests/php/unit/Settings/Abstracts/SettingsBaseTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.tests/php/unit/Settings/Abstracts/SettingsBaseTest.php b/.tests/php/unit/Settings/Abstracts/SettingsBaseTest.php index 7d41be0a..d425dd33 100644 --- a/.tests/php/unit/Settings/Abstracts/SettingsBaseTest.php +++ b/.tests/php/unit/Settings/Abstracts/SettingsBaseTest.php @@ -2838,7 +2838,7 @@ public function dp_test_pre_update_option_filter(): array { ], [ 'some_file' => 'a.xml' ], [ 'some_file' => 'b.xml' ], - [ '_network_wide' => [] ] + [ '_network_wide' => [] ], ], [ [], From 65e0a5628f65bbd60cf8133f143ced1d388a3cbc Mon Sep 17 00:00:00 2001 From: kagg-design Date: Sat, 10 Aug 2024 18:14:45 +0300 Subject: [PATCH 14/59] Block admin code execution on frontend. --- .../Settings/Abstracts/SettingsBaseTest.php | 21 +++++++++++++++++++ .tests/php/unit/Settings/IntegrationsTest.php | 1 + src/php/Settings/Abstracts/SettingsBase.php | 2 +- 3 files changed, 23 insertions(+), 1 deletion(-) diff --git a/.tests/php/unit/Settings/Abstracts/SettingsBaseTest.php b/.tests/php/unit/Settings/Abstracts/SettingsBaseTest.php index d425dd33..8b5eddd2 100644 --- a/.tests/php/unit/Settings/Abstracts/SettingsBaseTest.php +++ b/.tests/php/unit/Settings/Abstracts/SettingsBaseTest.php @@ -162,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(). * 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/src/php/Settings/Abstracts/SettingsBase.php b/src/php/Settings/Abstracts/SettingsBase.php index 5f2435fc..b47b22f5 100644 --- a/src/php/Settings/Abstracts/SettingsBase.php +++ b/src/php/Settings/Abstracts/SettingsBase.php @@ -260,7 +260,7 @@ public function init(): void { $this->form_fields(); $this->init_settings(); - if ( $this->is_main_menu_page() || $this->is_tab_active( $this ) ) { + if ( is_admin() && ( $this->is_main_menu_page() || $this->is_tab_active( $this ) ) ) { $this->init_hooks(); } } From 86b4c8d89055095b3c3ee5df6457b44778f980f6 Mon Sep 17 00:00:00 2001 From: kagg-design Date: Tue, 13 Aug 2024 21:48:52 +0300 Subject: [PATCH 15/59] Update readme.txt. --- readme.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.txt b/readme.txt index 021b1545..3ba07373 100644 --- a/readme.txt +++ b/readme.txt @@ -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. ` /** From 4ce1e6fa96a870d28f0c90917ae367ab8bcf8789 Mon Sep 17 00:00:00 2001 From: kagg-design Date: Wed, 14 Aug 2024 10:11:41 +0300 Subject: [PATCH 16/59] Fix unconditional forcing hCaptcha in Jetpack forms. --- .tests/php/integration/Jetpack/JetpackFormTest.php | 1 - readme.txt | 1 + src/php/Jetpack/JetpackForm.php | 2 -- 3 files changed, 1 insertion(+), 3 deletions(-) diff --git a/.tests/php/integration/Jetpack/JetpackFormTest.php b/.tests/php/integration/Jetpack/JetpackFormTest.php index a57d8135..eff958fe 100644 --- a/.tests/php/integration/Jetpack/JetpackFormTest.php +++ b/.tests/php/integration/Jetpack/JetpackFormTest.php @@ -43,7 +43,6 @@ public function dp_test_add_captcha(): array { $args = [ 'action' => 'hcaptcha_jetpack', 'name' => 'hcaptcha_jetpack_nonce', - 'force' => true, 'id' => [ 'source' => [ 'jetpack/jetpack.php' ], 'form_id' => 'contact', diff --git a/readme.txt b/readme.txt index 3ba07373..a84d5fef 100644 --- a/readme.txt +++ b/readme.txt @@ -567,6 +567,7 @@ Instructions for popular native integrations are below: = 4.5.0 = * Fixed frontend error when recaptcha was activated in wpDiscuz. +* Fixed unconditional forcing hCaptcha in Jetpack forms. = 4.4.0 = * Added compatibility with Contact Form 7 Stripe integration. diff --git a/src/php/Jetpack/JetpackForm.php b/src/php/Jetpack/JetpackForm.php index b3237f01..1a96cd9f 100644 --- a/src/php/Jetpack/JetpackForm.php +++ b/src/php/Jetpack/JetpackForm.php @@ -54,7 +54,6 @@ public function classic_callback( array $matches ): string { $args = [ 'action' => self::ACTION, 'name' => self::NAME, - 'force' => true, 'id' => [ 'source' => HCaptcha::get_class_source( __CLASS__ ), 'form_id' => 'contact', @@ -81,7 +80,6 @@ public function block_callback( array $matches ): string { $args = [ 'action' => self::ACTION, 'name' => self::NAME, - 'force' => true, 'id' => [ 'source' => HCaptcha::get_class_source( __CLASS__ ), 'form_id' => 'contact', From 54d238839fde446eb6f0c9741898115285bc346c Mon Sep 17 00:00:00 2001 From: kagg-design Date: Wed, 14 Aug 2024 11:07:00 +0300 Subject: [PATCH 17/59] Add support for Jetpack forms in block theme templates. --- readme.txt | 1 + src/php/Jetpack/JetpackBase.php | 6 +++++ src/php/Jetpack/JetpackForm.php | 43 ++++++++++++++++++--------------- 3 files changed, 31 insertions(+), 19 deletions(-) diff --git a/readme.txt b/readme.txt index a84d5fef..017c70b1 100644 --- a/readme.txt +++ b/readme.txt @@ -566,6 +566,7 @@ Instructions for popular native integrations are below: == Changelog == = 4.5.0 = +* Added support for Jetpack forms in block theme templates. * Fixed frontend error when recaptcha was activated in wpDiscuz. * Fixed unconditional forcing hCaptcha in Jetpack forms. diff --git a/src/php/Jetpack/JetpackBase.php b/src/php/Jetpack/JetpackBase.php index f3853797..4333551c 100644 --- a/src/php/Jetpack/JetpackBase.php +++ b/src/php/Jetpack/JetpackBase.php @@ -45,7 +45,13 @@ public function __construct() { * @return void */ private function init_hooks(): void { + // This filter works for a Jetpack block form on a page or in a template. + add_filter( 'jetpack_contact_form_html', [ $this, 'add_captcha' ] ); + + // This filter works for a Jetpack classic form. add_filter( 'the_content', [ $this, 'add_captcha' ] ); + + // This filter works for a Jetpack form in a classic widget. add_filter( 'widget_text', [ $this, 'add_captcha' ], 0 ); add_filter( 'widget_text', 'shortcode_unautop' ); diff --git a/src/php/Jetpack/JetpackForm.php b/src/php/Jetpack/JetpackForm.php index 1a96cd9f..da445db4 100644 --- a/src/php/Jetpack/JetpackForm.php +++ b/src/php/Jetpack/JetpackForm.php @@ -47,22 +47,13 @@ public function add_captcha( $content ): string { * @return string */ public function classic_callback( array $matches ): string { - if ( has_shortcode( $matches[0], 'hcaptcha' ) ) { + $hcaptcha = $this->prepare_hcaptcha( $matches ); + + if ( '' === $hcaptcha ) { return $matches[0]; } - $args = [ - 'action' => self::ACTION, - 'name' => self::NAME, - 'id' => [ - 'source' => HCaptcha::get_class_source( __CLASS__ ), - 'form_id' => 'contact', - ], - ]; - - $hcaptcha = '
' . HCaptcha::form( $args ) . '
'; - - return $matches[1] . $this->error_message( $hcaptcha ) . $matches[2]; + return $matches[1] . $hcaptcha . $matches[2]; } /** @@ -73,10 +64,24 @@ public function classic_callback( array $matches ): string { * @return string */ public function block_callback( array $matches ): string { - if ( has_shortcode( $matches[0], 'hcaptcha' ) ) { + $hcaptcha = $this->prepare_hcaptcha( $matches ); + + if ( '' === $hcaptcha ) { return $matches[0]; } + return str_replace( + $matches[1], + $hcaptcha . $matches[1], + $matches[0] + ); + } + + private function prepare_hcaptcha( array $matches ): string { + if ( has_shortcode( $matches[0], 'hcaptcha' ) ) { + return ''; + } + $args = [ 'action' => self::ACTION, 'name' => self::NAME, @@ -88,10 +93,10 @@ public function block_callback( array $matches ): string { $hcaptcha = '
' . HCaptcha::form( $args ) . '
'; - return str_replace( - $matches[1], - $this->error_message( $hcaptcha ) . $matches[1], - $matches[0] - ); + if ( false !== strpos( $matches[0], $hcaptcha ) ) { + return ''; + } + + return $this->error_message( $hcaptcha ); } } From db0a9639468e33a9ff0c93614c31eb6a20653f41 Mon Sep 17 00:00:00 2001 From: kagg-design Date: Wed, 14 Aug 2024 12:35:32 +0300 Subject: [PATCH 18/59] Fix error messaging when there are several Jetpack forms on the same page. --- .../integration/Jetpack/JetpackBaseTest.php | 9 +++- readme.txt | 4 +- src/php/Jetpack/JetpackBase.php | 50 +++++++++++++++++-- src/php/Jetpack/JetpackForm.php | 6 ++- src/php/includes/functions.php | 9 ++-- 5 files changed, 66 insertions(+), 12 deletions(-) diff --git a/.tests/php/integration/Jetpack/JetpackBaseTest.php b/.tests/php/integration/Jetpack/JetpackBaseTest.php index 5eff0507..54054ede 100644 --- a/.tests/php/integration/Jetpack/JetpackBaseTest.php +++ b/.tests/php/integration/Jetpack/JetpackBaseTest.php @@ -85,12 +85,19 @@ 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(); self::assertSame( $hcaptcha_content, $subject->error_message( $hcaptcha_content ) ); $this->set_protected_property( $subject, 'error_message', $error_message ); + $this->set_protected_property( $subject, 'error_form_hash', $error_form_hash ); $expected = $hcaptcha_content . '
@@ -100,7 +107,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/readme.txt b/readme.txt index 017c70b1..28f09a0f 100644 --- a/readme.txt +++ b/readme.txt @@ -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'` @@ -567,6 +567,8 @@ Instructions for popular native integrations are below: = 4.5.0 = * Added support for Jetpack forms in block theme templates. +* Added the second argument $atts to the 'hcap_hcaptcha_content' filter. +* Fixed error messaging when there are several Jetpack forms on the same page. * Fixed frontend error when recaptcha was activated in wpDiscuz. * Fixed unconditional forcing hCaptcha in Jetpack forms. diff --git a/src/php/Jetpack/JetpackBase.php b/src/php/Jetpack/JetpackBase.php index 4333551c..0b7036b5 100644 --- a/src/php/Jetpack/JetpackBase.php +++ b/src/php/Jetpack/JetpackBase.php @@ -32,6 +32,13 @@ abstract class JetpackBase { */ protected $error_message; + /** + * Errored form hash. + * + * @var string|null + */ + protected $error_form_hash; + /** * Constructor. */ @@ -88,7 +95,10 @@ public function verify( $is_spam = false ) { return $is_spam; } + $this->error_form_hash = $this->get_submitted_form_hash(); + $error = new WP_Error(); + $error->add( 'invalid_hcaptcha', $this->error_message ); add_filter( 'hcap_hcaptcha_content', [ $this, 'error_message' ] ); @@ -98,13 +108,21 @@ public function verify( $is_spam = false ) { /** * Print error message. * - * @param string|mixed $hcaptcha_content Content of hCaptcha. + * @param string|mixed $hcaptcha The hCaptcha form. + * @param array $atts The hCaptcha shortcode attributes. * * @return string|mixed */ - public function error_message( $hcaptcha_content = '' ) { + public function error_message( $hcaptcha = '', array $atts = [] ) { if ( null === $this->error_message ) { - return $hcaptcha_content; + return $hcaptcha; + } + + $hash = $atts['id']['form_id'] ?? ''; + $hash = str_replace( 'contact_', '', $hash ); + + if ( $hash !== $this->error_form_hash ) { + return $hcaptcha; } $message = <<< HTML @@ -117,7 +135,7 @@ public function error_message( $hcaptcha_content = '' ) { HTML; - return $hcaptcha_content . $message; + return $hcaptcha . $message; } /** @@ -136,4 +154,28 @@ public function print_inline_styles(): void { HCaptcha::css_display( $css ); } + + /** + * Get form hash. + * + * @param string $form Jetpack form. + * + * @return string + */ + protected function get_form_hash( string $form ): string { + return preg_match( "/name='contact-form-hash' value='(.+)'/", $form, $m ) + ? '_' . $m[1] + : ''; + } + + /** + * Get form hash. + * + * @return string|null + */ + private function get_submitted_form_hash(): ?string { + return isset( $_POST['contact-form-hash'] ) + ? sanitize_text_field( wp_unslash( $_POST['contact-form-hash'] ) ) + : null; + } } diff --git a/src/php/Jetpack/JetpackForm.php b/src/php/Jetpack/JetpackForm.php index da445db4..3ab956ad 100644 --- a/src/php/Jetpack/JetpackForm.php +++ b/src/php/Jetpack/JetpackForm.php @@ -82,12 +82,14 @@ private function prepare_hcaptcha( array $matches ): string { return ''; } + $hash = $this->get_form_hash( $matches[0] ); + $args = [ 'action' => self::ACTION, 'name' => self::NAME, 'id' => [ 'source' => HCaptcha::get_class_source( __CLASS__ ), - 'form_id' => 'contact', + 'form_id' => 'contact' . $hash, ], ]; @@ -97,6 +99,6 @@ private function prepare_hcaptcha( array $matches ): string { return ''; } - return $this->error_message( $hcaptcha ); + return $this->error_message( $hcaptcha, $args ); } } diff --git a/src/php/includes/functions.php b/src/php/includes/functions.php index 8153df5d..0286bc70 100644 --- a/src/php/includes/functions.php +++ b/src/php/includes/functions.php @@ -10,7 +10,7 @@ /** * Display hCaptcha shortcode. * - * @param array|string $atts hcaptcha shortcode attributes. + * @param array|string $atts The hCaptcha shortcode attributes. * * @return string */ @@ -37,11 +37,12 @@ function hcap_shortcode( $atts ): string { ); /** - * Filters the content of the hcaptcha form. + * Filters the content of the hCaptcha form. * - * @param string $form The hcaptcha form. + * @param string $form The hCaptcha form. + * @param array $atts The hCaptcha shortcode attributes. */ - return (string) apply_filters( 'hcap_hcaptcha_content', HCaptcha::form( $atts ) ); + return (string) apply_filters( 'hcap_hcaptcha_content', HCaptcha::form( $atts ), $atts ); } add_shortcode( 'hcaptcha', 'hcap_shortcode' ); From bdc070553162b864e1a3f7acbba428dde58622e2 Mon Sep 17 00:00:00 2001 From: kagg-design Date: Wed, 14 Aug 2024 12:44:09 +0300 Subject: [PATCH 19/59] phpcs. --- src/php/Jetpack/JetpackForm.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/php/Jetpack/JetpackForm.php b/src/php/Jetpack/JetpackForm.php index 3ab956ad..c0837697 100644 --- a/src/php/Jetpack/JetpackForm.php +++ b/src/php/Jetpack/JetpackForm.php @@ -77,6 +77,11 @@ public function block_callback( array $matches ): string { ); } + /** + * Prepare hCaptcha. + * + * @param array $matches Matches. + */ private function prepare_hcaptcha( array $matches ): string { if ( has_shortcode( $matches[0], 'hcaptcha' ) ) { return ''; From e8f38580cd2fff0c08b44bd04b077a933b2f8742 Mon Sep 17 00:00:00 2001 From: kagg-design Date: Wed, 14 Aug 2024 12:52:41 +0300 Subject: [PATCH 20/59] phpcs. --- src/php/Jetpack/JetpackBase.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/php/Jetpack/JetpackBase.php b/src/php/Jetpack/JetpackBase.php index 0b7036b5..94842155 100644 --- a/src/php/Jetpack/JetpackBase.php +++ b/src/php/Jetpack/JetpackBase.php @@ -118,7 +118,7 @@ public function error_message( $hcaptcha = '', array $atts = [] ) { return $hcaptcha; } - $hash = $atts['id']['form_id'] ?? ''; + $hash = $atts['id']['form_id'] ?? ''; $hash = str_replace( 'contact_', '', $hash ); if ( $hash !== $this->error_form_hash ) { @@ -174,8 +174,10 @@ protected function get_form_hash( string $form ): string { * @return string|null */ private function get_submitted_form_hash(): ?string { + // phpcs:disable WordPress.Security.NonceVerification.Missing return isset( $_POST['contact-form-hash'] ) ? sanitize_text_field( wp_unslash( $_POST['contact-form-hash'] ) ) : null; + // phpcs:enable WordPress.Security.NonceVerification.Missing } } From 1640f3aae29558b9e45b52f6eec7cd6565893484 Mon Sep 17 00:00:00 2001 From: kagg-design Date: Wed, 14 Aug 2024 20:14:24 +0300 Subject: [PATCH 21/59] Return Jetpack test coverage to 100%. --- .../integration/Jetpack/JetpackBaseTest.php | 24 ++++++++++++++++++ .../integration/Jetpack/JetpackFormTest.php | 25 +++++++++++++------ 2 files changed, 42 insertions(+), 7 deletions(-) diff --git a/.tests/php/integration/Jetpack/JetpackBaseTest.php b/.tests/php/integration/Jetpack/JetpackBaseTest.php index 54054ede..71f69730 100644 --- a/.tests/php/integration/Jetpack/JetpackBaseTest.php +++ b/.tests/php/integration/Jetpack/JetpackBaseTest.php @@ -20,6 +20,17 @@ */ class JetpackBaseTest extends HCaptchaWPTestCase { + /** + * Tear down test. + * + * @return void + */ + public function tearDown(): void { + unset( $_POST['contact-form-hash'] ); + + parent::tearDown(); + } + /** * Test constructor and init_hooks. */ @@ -64,8 +75,11 @@ public function test_jetpack_verify(): void { /** * Test jetpack_verify() not verified. + * + * @throws ReflectionException */ public function test_jetpack_verify_not_verified(): void { + $hash = 'some hash'; $error = new WP_Error( 'invalid_hcaptcha', 'The hCaptcha is invalid.' ); $this->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' ] ) ); } @@ -97,6 +118,9 @@ public function test_error_message(): void { self::assertSame( $hcaptcha_content, $subject->error_message( $hcaptcha_content ) ); $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 . '
diff --git a/.tests/php/integration/Jetpack/JetpackFormTest.php b/.tests/php/integration/Jetpack/JetpackFormTest.php index eff958fe..5828bd37 100644 --- a/.tests/php/integration/Jetpack/JetpackFormTest.php +++ b/.tests/php/integration/Jetpack/JetpackFormTest.php @@ -40,7 +40,9 @@ 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', 'id' => [ @@ -48,30 +50,39 @@ public function dp_test_add_captcha(): array { '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]', + '[contact-form] Some contact form
' . $classic_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
', - '
' . $hcaptcha . '
Contact Us', + '
Contact Us' . $hash_input . '
', + '
' . $hcaptcha . '
Contact Us' . $hash_input . '', ], 'Block contact form with hcaptcha' => [ '
Contact Us
', '
Contact Us
', ], 'Block contact form and search form' => [ - '
Contact Us
' . + '
Contact Us' . $hash_input . '
' . '
', - '
' . $hcaptcha . '
Contact Us' . + '
' . $hcaptcha . '
Contact Us' . $hash_input . '' . '
', ], ]; From 21655a31aa3b7beba67f1b3c1fb1f3d2625f71c9 Mon Sep 17 00:00:00 2001 From: kagg-design Date: Wed, 14 Aug 2024 23:52:19 +0300 Subject: [PATCH 22/59] phpcs. --- .tests/php/integration/Jetpack/JetpackBaseTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.tests/php/integration/Jetpack/JetpackBaseTest.php b/.tests/php/integration/Jetpack/JetpackBaseTest.php index 71f69730..490ab78f 100644 --- a/.tests/php/integration/Jetpack/JetpackBaseTest.php +++ b/.tests/php/integration/Jetpack/JetpackBaseTest.php @@ -76,7 +76,7 @@ public function test_jetpack_verify(): void { /** * Test jetpack_verify() not verified. * - * @throws ReflectionException + * @throws ReflectionException ReflectionExceptionю */ public function test_jetpack_verify_not_verified(): void { $hash = 'some hash'; From a4f7c6acc52f94abcabbad6113fb6a9b5d80734d Mon Sep 17 00:00:00 2001 From: kagg-design Date: Thu, 15 Aug 2024 09:48:41 +0300 Subject: [PATCH 23/59] phpcs. --- .tests/php/integration/Jetpack/JetpackBaseTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.tests/php/integration/Jetpack/JetpackBaseTest.php b/.tests/php/integration/Jetpack/JetpackBaseTest.php index 490ab78f..62e3983d 100644 --- a/.tests/php/integration/Jetpack/JetpackBaseTest.php +++ b/.tests/php/integration/Jetpack/JetpackBaseTest.php @@ -76,7 +76,7 @@ public function test_jetpack_verify(): void { /** * Test jetpack_verify() not verified. * - * @throws ReflectionException ReflectionExceptionю + * @throws ReflectionException ReflectionException. */ public function test_jetpack_verify_not_verified(): void { $hash = 'some hash'; From 06c2d815996bc30d2c3f34707a5cb87aeea6c058 Mon Sep 17 00:00:00 2001 From: kagg-design Date: Thu, 15 Aug 2024 11:52:19 +0300 Subject: [PATCH 24/59] Add ability to test not final versions. Fix hCaptcha nonce error on MailPoet admin pages. Fix tests. Add LostPassword form support for bbPress. Make bbPress forms working when Login/Register are off. Add Register form support for bbPress. Add Login form support for bbPress. WPCS in Divi.php. Fix error messaging for several classic Jetpack forms. --- .tests/php/integration/AAAMainTest.php | 50 +++++-- .../integration/Jetpack/JetpackBaseTest.php | 2 +- .../integration/Jetpack/JetpackFormTest.php | 4 +- .tests/php/integration/MainPluginFileTest.php | 8 ++ .tests/php/unit/HCaptchaTestCase.php | 3 + .tests/php/unit/Settings/SystemInfoTest.php | 3 + hcaptcha.php | 4 +- readme.txt | 8 +- src/php/BBPress/Login.php | 73 +++++++++++ src/php/BBPress/LostPassword.php | 96 ++++++++++++++ src/php/BBPress/Register.php | 124 ++++++++++++++++++ src/php/Divi/Login.php | 9 +- src/php/Helpers/Request.php | 59 ++++++++- src/php/Jetpack/JetpackBase.php | 5 +- src/php/Jetpack/JetpackForm.php | 56 ++------ src/php/MailPoet/Form.php | 3 +- src/php/Main.php | 15 +++ src/php/Settings/Integrations.php | 3 + 18 files changed, 453 insertions(+), 72 deletions(-) create mode 100644 src/php/BBPress/Login.php create mode 100644 src/php/BBPress/LostPassword.php create mode 100644 src/php/BBPress/Register.php diff --git a/.tests/php/integration/AAAMainTest.php b/.tests/php/integration/AAAMainTest.php index 5ddd283e..6e9ab72a 100644 --- a/.tests/php/integration/AAAMainTest.php +++ b/.tests/php/integration/AAAMainTest.php @@ -109,7 +109,7 @@ 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( -PHP_INT_MAX, has_action( 'plugins_loaded', [ $hcaptcha, 'init_hooks' ] ) ); remove_action( 'plugins_loaded', [ $hcaptcha, 'init_hooks' ], -PHP_INT_MAX ); @@ -117,7 +117,7 @@ public function test_init(): void { $hcaptcha->init(); - self::assertSame( - PHP_INT_MAX, has_action( 'plugins_loaded', [ $hcaptcha, 'init_hooks' ] ) ); + self::assertSame( -PHP_INT_MAX, has_action( 'plugins_loaded', [ $hcaptcha, 'init_hooks' ] ) ); } /** @@ -166,9 +166,9 @@ static function () use ( $whitelisted ) { ) ); - self::assertSame( - PHP_INT_MAX, has_action( 'plugins_loaded', [ $hcaptcha, 'init_hooks' ] ) ); + self::assertSame( -PHP_INT_MAX, has_action( 'plugins_loaded', [ $hcaptcha, 'init_hooks' ] ) ); - self::assertSame( - PHP_INT_MAX + 1, has_action( 'plugins_loaded', [ $hcaptcha, 'load_modules' ] ) ); + self::assertSame( -PHP_INT_MAX + 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 +188,11 @@ static function () use ( $whitelisted ) { $subject->init_hooks(); self::assertSame( - - PHP_INT_MAX + 1, + -PHP_INT_MAX + 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 +285,11 @@ static function () { $subject->init_hooks(); self::assertSame( - - PHP_INT_MAX + 1, + -PHP_INT_MAX + 1, has_action( 'plugins_loaded', [ $subject, 'load_modules' ] ) ); self::assertSame( - - PHP_INT_MAX, + -PHP_INT_MAX, has_filter( 'hcap_whitelist_ip', [ $subject, 'whitelist_ip' ] @@ -613,7 +613,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 +931,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 +1303,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 +1724,7 @@ static function ( $override, $domain, $mofile ) use ( &$override_filter_params ) return $override; }, - - PHP_INT_MAX, + -PHP_INT_MAX, 3 ); diff --git a/.tests/php/integration/Jetpack/JetpackBaseTest.php b/.tests/php/integration/Jetpack/JetpackBaseTest.php index 62e3983d..ee32efde 100644 --- a/.tests/php/integration/Jetpack/JetpackBaseTest.php +++ b/.tests/php/integration/Jetpack/JetpackBaseTest.php @@ -39,7 +39,7 @@ public function test_init_hooks(): void { self::assertSame( 10, - has_filter( 'the_content', [ $subject, 'add_captcha' ] ) + has_filter( 'jetpack_contact_form_html', [ $subject, 'add_captcha' ] ) ); self::assertSame( 0, diff --git a/.tests/php/integration/Jetpack/JetpackFormTest.php b/.tests/php/integration/Jetpack/JetpackFormTest.php index 5828bd37..18fb99e1 100644 --- a/.tests/php/integration/Jetpack/JetpackFormTest.php +++ b/.tests/php/integration/Jetpack/JetpackFormTest.php @@ -64,8 +64,8 @@ public function dp_test_add_captcha(): array { return [ 'Empty contact form' => [ '', '' ], 'Classic contact form' => [ - '[contact-form] Some contact form [/contact-form]', - '[contact-form] Some contact form
' . $classic_hcaptcha . '
[/contact-form]', + '
Contact Us' . $hash_input . '
', + '
' . $hcaptcha . '
' . $hash_input . '', ], 'Classic contact form with hcaptcha' => [ '[contact-form] Some contact form [hcaptcha][/contact-form]', 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/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/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/hcaptcha.php b/hcaptcha.php index f94b2084..9121013d 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-RC1 * 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-RC1'; /** * Path to the plugin dir. diff --git a/readme.txt b/readme.txt index 28f09a0f..aed00028 100644 --- a/readme.txt +++ b/readme.txt @@ -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'` @@ -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 @@ -567,10 +567,12 @@ Instructions for popular native integrations are below: = 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. * Fixed error messaging when there are several Jetpack forms on the same page. -* Fixed frontend error when recaptcha was activated in wpDiscuz. * Fixed unconditional forcing hCaptcha in Jetpack forms. +* 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. 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 = '/(