From 44ef2d2a4b38483bce979a4c68b0aa2afb4d0a9a Mon Sep 17 00:00:00 2001 From: Rasmy Nguyen Date: Mon, 16 Dec 2024 12:18:54 -0500 Subject: [PATCH 1/5] fix(recaptcha): replace alerts with generic errors (#3627) This PR replaces the default recaptcha failure alert behavior with generic errors appended to the relevant forms. --- src/other-scripts/recaptcha/index.js | 52 ++++++++++++++++++++++---- src/other-scripts/recaptcha/style.scss | 6 +++ 2 files changed, 51 insertions(+), 7 deletions(-) diff --git a/src/other-scripts/recaptcha/index.js b/src/other-scripts/recaptcha/index.js index 427efccc98..a7f7a61904 100644 --- a/src/other-scripts/recaptcha/index.js +++ b/src/other-scripts/recaptcha/index.js @@ -95,6 +95,39 @@ function addHiddenField( form ) { } } +/** + * Append a generic error message above the given form. + * + * @param {HTMLElement} form The form element. + * @param {string} message The error message to display. + */ +function addErrorMessage( form, message ) { + const errorText = document.createElement( 'p' ); + errorText.textContent = message; + const container = document.createElement( 'div' ); + container.classList.add( 'newspack-recaptcha-error' ); + container.appendChild( errorText ); + // Newsletters block errors render below the form. + if ( form.parentElement.classList.contains( 'newspack-newsletters-subscribe' ) ) { + form.append( container ); + } else { + container.classList.add( 'newspack-ui__notice', 'newspack-ui__notice--error' ); + form.insertBefore( container, form.firstChild ); + } +} + +/** + * Remove generic error messages from form if present. + * + * @param {HTMLElement} form The form element. + */ +function removeErrorMessages( form ) { + const errors = form.querySelectorAll( '.newspack-recaptcha-error' ); + for ( const error of errors ) { + error.parentElement.removeChild( error ); + } +} + /** * Remove the hidden reCAPTCHA v3 token field from the given form. * @@ -168,6 +201,14 @@ function renderWidget( form, onSuccess = null, onError = null ) { refreshWidget( button ); }; + const errorCallback = ( message ) => { + if ( onError ) { + onError( message ); + } else { + addErrorMessage( form, message ); + } + } + // Render reCAPTCHA widget. See https://developers.google.com/recaptcha/docs/invisible#js_api for API reference. const widgetId = grecaptcha.render( button, { ...options, @@ -179,17 +220,12 @@ function renderWidget( form, onSuccess = null, onError = null ) { refreshWidget( button ); } else { clearInterval( refreshIntervalId ); + button.disabled = true; } const message = retryCount < 3 ? wp.i18n.__( 'There was an error connecting with reCAPTCHA. Please try submitting again.', 'newspack-plugin' ) : wp.i18n.__( 'There was an error connecting with reCAPTCHA. Please reload the page and try again.', 'newspack-plugin' ); - if ( onError ) { - onError( message ); - } else { - // Recaptcha's default error behavior is to alert with the above message. - // eslint-disable-next-line no-alert - alert( message ); - } + errorCallback( message ); }, 'expired-callback': () => { refreshWidget( button ); @@ -202,6 +238,8 @@ function renderWidget( form, onSuccess = null, onError = null ) { button.addEventListener( 'click', e => { e.preventDefault(); e.stopImmediatePropagation(); + // Empty error messages if present. + removeErrorMessages( form ); // Skip reCAPTCHA verification if the button has a data-skip-recaptcha attribute. if ( button.hasAttribute( 'data-skip-recaptcha' ) ) { successCallback(); diff --git a/src/other-scripts/recaptcha/style.scss b/src/other-scripts/recaptcha/style.scss index 52fdd4451e..b6bfb43f17 100644 --- a/src/other-scripts/recaptcha/style.scss +++ b/src/other-scripts/recaptcha/style.scss @@ -13,3 +13,9 @@ inset: 0 !important; // Prevents an odd side-scroll issue in the registration modal & block. visibility: hidden !important; } + +.newspack-recaptcha-error { + &.newspack-ui__notice--error { + margin-top: 0 !important; + } +} From 7a60de1a641a3ab0ebd155bea36b7112c8e5b8c6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 30 Dec 2024 09:10:44 +0000 Subject: [PATCH 2/5] chore(deps-dev): bump lint-staged from 15.2.11 to 15.3.0 Bumps [lint-staged](https://github.com/lint-staged/lint-staged) from 15.2.11 to 15.3.0. - [Release notes](https://github.com/lint-staged/lint-staged/releases) - [Changelog](https://github.com/lint-staged/lint-staged/blob/master/CHANGELOG.md) - [Commits](https://github.com/lint-staged/lint-staged/compare/v15.2.11...v15.3.0) --- updated-dependencies: - dependency-name: lint-staged dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- package-lock.json | 15 ++++++++------- package.json | 2 +- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/package-lock.json b/package-lock.json index 0ac341fa00..c1465a411c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -27,7 +27,7 @@ "@types/react": "^17.0.75", "@wordpress/browserslist-config": "^6.14.0", "eslint": "^8.57.0", - "lint-staged": "^15.2.11", + "lint-staged": "^15.3.0", "newspack-scripts": "^5.5.2", "postcss-scss": "^4.0.9" } @@ -16295,12 +16295,12 @@ } }, "node_modules/lint-staged": { - "version": "15.2.11", - "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-15.2.11.tgz", - "integrity": "sha512-Ev6ivCTYRTGs9ychvpVw35m/bcNDuBN+mnTeObCL5h+boS5WzBEC6LHI4I9F/++sZm1m+J2LEiy0gxL/R9TBqQ==", + "version": "15.3.0", + "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-15.3.0.tgz", + "integrity": "sha512-vHFahytLoF2enJklgtOtCtIjZrKD/LoxlaUusd5nh7dWv/dkKQJY74ndFSzxCdv7g0ueGg1ORgTSt4Y9LPZn9A==", "dev": true, "dependencies": { - "chalk": "~5.3.0", + "chalk": "~5.4.1", "commander": "~12.1.0", "debug": "~4.4.0", "execa": "~8.0.1", @@ -16322,9 +16322,10 @@ } }, "node_modules/lint-staged/node_modules/chalk": { - "version": "5.3.0", + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.4.1.tgz", + "integrity": "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==", "dev": true, - "license": "MIT", "engines": { "node": "^12.17.0 || ^14.13 || >=16.0.0" }, diff --git a/package.json b/package.json index ad3d94027d..5952c9471d 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,7 @@ "@types/react": "^17.0.75", "@wordpress/browserslist-config": "^6.14.0", "eslint": "^8.57.0", - "lint-staged": "^15.2.11", + "lint-staged": "^15.3.0", "newspack-scripts": "^5.5.2", "postcss-scss": "^4.0.9" }, From 770ab18f88e7d42fc3f8165f3c8e10193f641e6c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 6 Jan 2025 09:40:39 +0000 Subject: [PATCH 3/5] chore(deps-dev): bump @wordpress/browserslist-config Bumps [@wordpress/browserslist-config](https://github.com/WordPress/gutenberg/tree/HEAD/packages/browserslist-config) from 6.14.0 to 6.15.0. - [Release notes](https://github.com/WordPress/gutenberg/releases) - [Changelog](https://github.com/WordPress/gutenberg/blob/trunk/packages/browserslist-config/CHANGELOG.md) - [Commits](https://github.com/WordPress/gutenberg/commits/@wordpress/browserslist-config@6.15.0/packages/browserslist-config) --- updated-dependencies: - dependency-name: "@wordpress/browserslist-config" dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- package-lock.json | 8 ++++---- package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index c1465a411c..0c008281e2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -25,7 +25,7 @@ "@testing-library/react": "^12.1.4", "@types/qs": "^6.9.17", "@types/react": "^17.0.75", - "@wordpress/browserslist-config": "^6.14.0", + "@wordpress/browserslist-config": "^6.15.0", "eslint": "^8.57.0", "lint-staged": "^15.3.0", "newspack-scripts": "^5.5.2", @@ -6162,9 +6162,9 @@ } }, "node_modules/@wordpress/browserslist-config": { - "version": "6.14.0", - "resolved": "https://registry.npmjs.org/@wordpress/browserslist-config/-/browserslist-config-6.14.0.tgz", - "integrity": "sha512-a26hxY8R/A7FH/Z8oZsYS31ZC/Xy9QSBTi5w84MKSeYdWlck7t1QdCwUNF1u621wbuP7beiiu9FkYY4hI3Bk9A==", + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/@wordpress/browserslist-config/-/browserslist-config-6.15.0.tgz", + "integrity": "sha512-JmpThXSvE/ZsihJ/GOBmZZUEgVN78xWIPDacRzEXCPp9FA4UuJpXgx1JY3nWe4L3FyB6XIz+Klk0TfEwfL8S2w==", "dev": true, "engines": { "node": ">=18.12.0", diff --git a/package.json b/package.json index 5952c9471d..e974751cc5 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,7 @@ "@testing-library/react": "^12.1.4", "@types/qs": "^6.9.17", "@types/react": "^17.0.75", - "@wordpress/browserslist-config": "^6.14.0", + "@wordpress/browserslist-config": "^6.15.0", "eslint": "^8.57.0", "lint-staged": "^15.3.0", "newspack-scripts": "^5.5.2", From 510a1a04881c9bccf3389ecd6dbf6b82a13d461b Mon Sep 17 00:00:00 2001 From: Miguel Peixe Date: Wed, 8 Jan 2025 19:27:36 -0300 Subject: [PATCH 4/5] perf(data-events): queue dispatches to execute on shutdown (#3616) --- includes/data-events/class-data-events.php | 64 ++++++++++++++++++---- tests/unit-tests/data-events.php | 28 +++++++++- 2 files changed, 79 insertions(+), 13 deletions(-) diff --git a/includes/data-events/class-data-events.php b/includes/data-events/class-data-events.php index 534a056103..acee8a53b9 100644 --- a/includes/data-events/class-data-events.php +++ b/includes/data-events/class-data-events.php @@ -37,15 +37,22 @@ final class Data_Events { */ private static $global_handlers = []; + /** + * Dispatches queued for execution on shutdown. + * + * @var array[] + */ + private static $queued_dispatches = []; + /** * Initialize hooks. */ public static function init() { \add_action( 'wp_ajax_' . self::ACTION, [ __CLASS__, 'maybe_handle' ] ); \add_action( 'wp_ajax_nopriv_' . self::ACTION, [ __CLASS__, 'maybe_handle' ] ); + \add_action( 'shutdown', [ __CLASS__, 'execute_queued_dispatches' ] ); } - /** * Maybe handle an event. */ @@ -57,16 +64,24 @@ public static function maybe_handle() { \wp_die(); } - $action_name = isset( $_POST['action_name'] ) ? \sanitize_text_field( \wp_unslash( $_POST['action_name'] ) ) : null; - if ( empty( $action_name ) || ! isset( self::$actions[ $action_name ] ) ) { + $dispatches = isset( $_POST['dispatches'] ) ? $_POST['dispatches'] : null; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized + + if ( empty( $dispatches ) || ! is_array( $dispatches ) ) { \wp_die(); } - $timestamp = isset( $_POST['timestamp'] ) ? \sanitize_text_field( \wp_unslash( $_POST['timestamp'] ) ) : null; - $data = isset( $_POST['data'] ) ? $_POST['data'] : null; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized - $client_id = isset( $_POST['client_id'] ) ? \sanitize_text_field( \wp_unslash( $_POST['client_id'] ) ) : null; + foreach ( $dispatches as $dispatch ) { + $action_name = isset( $dispatch['action_name'] ) ? \sanitize_text_field( $dispatch['action_name'] ) : null; + if ( empty( $action_name ) || ! isset( self::$actions[ $action_name ] ) ) { + continue; + } + + $timestamp = isset( $dispatch['timestamp'] ) ? \sanitize_text_field( $dispatch['timestamp'] ) : null; + $data = isset( $dispatch['data'] ) ? $dispatch['data'] : null; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized + $client_id = isset( $dispatch['client_id'] ) ? \sanitize_text_field( $dispatch['client_id'] ) : null; - self::handle( $action_name, $timestamp, $data, $client_id ); + self::handle( $action_name, $timestamp, $data, $client_id ); + } \wp_die(); } @@ -312,8 +327,26 @@ public static function dispatch( $action_name, $data, $use_client_id = true ) { return $body; } + self::$queued_dispatches[] = $body; + + // If we're in shutdown, execute the dispatches immediately. + if ( did_action( 'shutdown' ) ) { + self::execute_queued_dispatches(); + } + } + + /** + * Execute queued dispatches. + */ + public static function execute_queued_dispatches() { + if ( empty( self::$queued_dispatches ) ) { + return; + } + + $actions = array_column( self::$queued_dispatches, 'action_name' ); + Logger::log( - sprintf( 'Dispatching action "%s".', $action_name ), + sprintf( 'Dispatching actions: "%s".', implode( ', ', $actions ) ), self::LOGGER_HEADER ); @@ -325,16 +358,27 @@ public static function dispatch( $action_name, $data, $use_client_id = true ) { \admin_url( 'admin-ajax.php' ) ); - return \wp_remote_post( + $request = \wp_remote_post( $url, [ 'timeout' => 0.01, 'blocking' => false, - 'body' => $body, + 'body' => [ 'dispatches' => self::$queued_dispatches ], 'cookies' => $_COOKIE, // phpcs:ignore 'sslverify' => apply_filters( 'https_local_ssl_verify', false ), ] ); + + /** + * Fires after dispatching queued actions. + * + * @param WP_Error|WP_HTTP_Response $request The request object. + * @param array $queued_dispatches The queued dispatches. + */ + \do_action( 'newspack_data_events_dispatched', $request, self::$queued_dispatches ); + + // Clear the queue in case of a retry. + self::$queued_dispatches = []; } } Data_Events::init(); diff --git a/tests/unit-tests/data-events.php b/tests/unit-tests/data-events.php index f26ad61ced..fb922a7fb9 100644 --- a/tests/unit-tests/data-events.php +++ b/tests/unit-tests/data-events.php @@ -74,10 +74,32 @@ public function test_dispatch() { // Assert the hook was called once. $this->assertEquals( 1, $call_count ); + } + + /** + * Test that executing queued dispatches triggers the dispatched action hook. + */ + public function test_execute_queued_dispatches() { + $action_name = 'test_action'; + $data = [ 'test' => 'data' ]; + + $hook_request = null; + $hook_queued_dispatches = null; + + $hook = function( $request, $queued_dispatches ) use ( &$hook_request, &$hook_queued_dispatches ) { + $hook_request = $request; + $hook_queued_dispatches = $queued_dispatches; + }; + add_action( 'newspack_data_events_dispatched', $hook, 10, 2 ); + + Data_Events::register_action( $action_name ); + Data_Events::dispatch( $action_name, $data ); + Data_Events::execute_queued_dispatches(); - // Assert it returns a WP_Http response. - $this->assertIsArray( $result ); - $this->assertArrayHasKey( 'http_response', $result ); + $this->assertIsArray( $hook_request ); + $this->assertIsArray( $hook_queued_dispatches ); + $this->assertEquals( $action_name, $hook_queued_dispatches[0]['action_name'] ); + $this->assertEquals( $data, $hook_queued_dispatches[0]['data'] ); } /** From c639af78b9ba7cf73ef01e7f499a2389e0626343 Mon Sep 17 00:00:00 2001 From: Derrick Koo Date: Thu, 9 Jan 2025 09:19:50 -0700 Subject: [PATCH 5/5] fix(cli): verify-reader CLI command (#3660) --- includes/cli/class-ras.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/includes/cli/class-ras.php b/includes/cli/class-ras.php index 1964f70506..e3ebc61902 100644 --- a/includes/cli/class-ras.php +++ b/includes/cli/class-ras.php @@ -55,14 +55,14 @@ public static function cli_setup_ras() { * * ## OPTIONS * - * [--user=] + * [--user-id=] * : The user ID or email address associated with the reader account to verify. * * @param array $args Positional args. * @param array $assoc_args Associative args. */ public static function cli_verify_reader( $args, $assoc_args ) { - $user_id_or_email = ! empty( $args ) ? reset( $args ) : false; + $user_id_or_email = ! empty( $assoc_args ) ? reset( $assoc_args ) : false; if ( ! $user_id_or_email ) { WP_CLI::error( __( 'Please provide a user ID or email address.', 'newspack-plugin' ) ); exit;