Merge pull request #32540 from woocommerce/fix/errors-flash-messaging

Reduce risk of prematurely dropping meta box error messages
This commit is contained in:
Barry Hughes 2022-04-25 12:32:38 -07:00 committed by GitHub
commit 004b007199
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 107 additions and 6 deletions

View File

@ -0,0 +1,4 @@
Significance: minor
Type: tweak
Changes to reduce the risk of admin error messages being discarded prematurely.

View File

@ -15,6 +15,12 @@ defined( 'ABSPATH' ) || exit;
* WC_Admin_Meta_Boxes.
*/
class WC_Admin_Meta_Boxes {
/**
* Name of the option used to store errors to be displayed at the next suitable opportunity.
*
* @since 6.5.0
*/
public const ERROR_STORE = 'woocommerce_meta_box_errors';
/**
* Is meta boxes saved once?
@ -66,7 +72,7 @@ class WC_Admin_Meta_Boxes {
// Error handling (for showing errors from meta boxes on next page load).
add_action( 'admin_notices', array( $this, 'output_errors' ) );
add_action( 'shutdown', array( $this, 'save_errors' ) );
add_action( 'shutdown', array( $this, 'append_to_error_store' ) );
add_filter( 'theme_product_templates', array( $this, 'remove_block_templates' ), 10, 1 );
}
@ -82,16 +88,34 @@ class WC_Admin_Meta_Boxes {
/**
* Save errors to an option.
*
* Note that calling this will overwrite any errors that have already been stored via the Options API.
* Unless you are sure you want this, consider using the append_to_error_store() method instead.
*/
public function save_errors() {
update_option( 'woocommerce_meta_box_errors', self::$meta_box_errors );
update_option( self::ERROR_STORE, self::$meta_box_errors );
}
/**
* If additional errors have been added in the current request (ie, via the add_error() method) then they
* will be added to the persistent error store via the Options API.
*
* @since 6.5.0
*/
public function append_to_error_store() {
if ( empty( self::$meta_box_errors ) ) {
return;
}
$existing_errors = get_option( self::ERROR_STORE, array() );
update_option( self::ERROR_STORE, array_unique( array_merge( $existing_errors, self::$meta_box_errors ) ) );
}
/**
* Show any stored error messages.
*/
public function output_errors() {
$errors = array_filter( (array) get_option( 'woocommerce_meta_box_errors' ) );
$errors = array_filter( (array) get_option( self::ERROR_STORE ) );
if ( ! empty( $errors ) ) {
@ -104,7 +128,7 @@ class WC_Admin_Meta_Boxes {
echo '</div>';
// Clear.
delete_option( 'woocommerce_meta_box_errors' );
delete_option( self::ERROR_STORE );
}
}

View File

@ -2226,7 +2226,7 @@ class WC_AJAX {
echo '<button type="button" class="notice-dismiss"><span class="screen-reader-text">' . esc_html__( 'Dismiss this notice.', 'woocommerce' ) . '</span></button>';
echo '</div>';
delete_option( 'woocommerce_meta_box_errors' );
delete_option( WC_Admin_Meta_Boxes::ERROR_STORE );
}
wp_die();
@ -3095,7 +3095,7 @@ class WC_AJAX {
}
$enabled = $gateway->get_option( 'enabled', 'no' );
$option = array(
'id' => $gateway->get_option_key()
'id' => $gateway->get_option_key(),
);
if ( ! wc_string_to_bool( $enabled ) ) {

View File

@ -0,0 +1,73 @@
<?php
/**
* Tests for meta box-related functionality in the product editor.
*/
class WC_Admin_Meta_Boxes_Test extends WC_Unit_Test_Case {
/**
* @var WC_Admin_Meta_Boxes
*/
private $sut;
/**
* Create subject-under-test.
*/
public function set_up() {
$this->sut = new WC_Admin_Meta_Boxes();
parent::set_up();
}
/**
* @testdox Test that meta box errors can be stored and retrieved as expected.
*/
public function test_persistence_of_meta_box_errors() {
WC_Admin_Meta_Boxes::add_error( 'Oh no!' );
WC_Admin_Meta_Boxes::add_error( 'Crikey!' );
$error_output = $this->get_meta_box_error_output();
$this->assertEmpty( $error_output, 'If the errors have not first been saved to the database, they cannot be retrieved for display.' );
$this->simulate_shutdown();
$error_output = $this->get_meta_box_error_output();
$this->assertStringContainsString( 'Oh no!', $error_output, 'The error output contains the expected error string (test #1).' );
$this->assertStringContainsString( 'Crikey!', $error_output, 'The error output contains the expected error string (test #2).' );
$error_output = $this->get_meta_box_error_output();
$this->assertEmpty( $error_output, 'The error store is cleared after errors have been output.' );
}
/**
* @testdox Test that the stored meta box errors are not accidentally cleared by concurrent requests before they are rendered.
*/
public function test_meta_box_errors_are_not_accidentally_cleared_during_shutdown() {
WC_Admin_Meta_Boxes::add_error( 'Yikes!' );
$this->simulate_shutdown();
$this->simulate_shutdown();
$error_output = $this->get_meta_box_error_output();
$this->assertStringContainsString( 'Yikes!', $error_output, 'The stored error persisted across requests.' );
}
/**
* Calls the WC_Admin_Meta_Boxes::output_errors() method, capturing and returning the output.
*
* @return string
*/
private function get_meta_box_error_output(): string {
ob_start();
$this->sut->output_errors();
return ob_get_clean();
}
/**
* Simulates what normally happens when `shutdown` occurs, in relation to the WC_Admin_Meta_Boxes class.
* We avoid actually calling `do_action( 'shutdown' )` because we do not have perfect isolation between tests, and
* wish to avoid unwanted side-effects unrelated to this set of tests.
*/
private function simulate_shutdown() {
// Previously (prior to 6.5.0), $this->sut->save_errors() would have been called during shutdown.
$this->sut->append_to_error_store();
WC_Admin_Meta_Boxes::$meta_box_errors = array();
}
}