From fb334f3e304daebc5f739621236995d25e547f4b Mon Sep 17 00:00:00 2001 From: Igor Zinovyev Date: Tue, 2 Jun 2026 19:48:42 +0300 Subject: [PATCH] DataView: add `notices` config to suppress built-in save notices MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When a DataView page lives under the WordPress "Settings" menu, core's options-head.php already renders a native "Settings saved." notice for the `updated=1` redirect (its documented back-compat path for plugins that don't use the Settings API). DataView's own render_notices() also fires on `?updated`, so the notice appears twice — one plain notice and one with id="setting-error-settings_updated". Add a `notices` config option (default true). When false, render_notices() returns early, letting the host environment own save notices. Error/validation output (render_errors) is a separate path and unaffected. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/DataView/DataViewConfig.php | 13 +++++++ src/DataView/RequestRouter.php | 4 ++ tests/phpunit/data-view.php | 69 +++++++++++++++++++++++++++++++++ 3 files changed, 86 insertions(+) diff --git a/src/DataView/DataViewConfig.php b/src/DataView/DataViewConfig.php index a4102d4..4aa4cd0 100644 --- a/src/DataView/DataViewConfig.php +++ b/src/DataView/DataViewConfig.php @@ -15,6 +15,18 @@ class DataViewConfig { public readonly string $capability; public readonly array $storage_options; + /** + * Whether DataView renders its own success/error notices. + * + * Set to false when the host environment already renders save notices for + * this page — e.g. a settings page under the WordPress "Settings" menu, + * where core's options-head.php shows a native "Settings saved." notice for + * the `updated=1` redirect. Leaving this true there would double the notice. + * + * @var bool + */ + public readonly bool $notices; + /** * Full field configurations including repeater sub-fields. * @@ -70,6 +82,7 @@ public function __construct( array $config ) { $this->mode = $config['mode'] ?? 'plural'; $this->capability = $config['capability'] ?? 'manage_options'; $this->storage_options = $config['storage_options'] ?? []; + $this->notices = $config['notices'] ?? true; // Parse field configurations (supports both simple and complex definitions). $this->field_configs = $this->parse_field_configs( $config['fields'] ); diff --git a/src/DataView/RequestRouter.php b/src/DataView/RequestRouter.php index c84f446..125acbb 100644 --- a/src/DataView/RequestRouter.php +++ b/src/DataView/RequestRouter.php @@ -600,6 +600,10 @@ protected function render_back_link(): void { * Render success/error notices from query params. */ protected function render_notices(): void { + if ( ! $this->config->notices ) { + return; + } + // phpcs:ignore WordPress.Security.NonceVerification.Recommended if ( isset( $_GET['created'] ) ) { echo '

' . esc_html( $this->get_label( 'item_created' ) ) . '

'; diff --git a/tests/phpunit/data-view.php b/tests/phpunit/data-view.php index a56e46a..134ed7a 100644 --- a/tests/phpunit/data-view.php +++ b/tests/phpunit/data-view.php @@ -380,6 +380,75 @@ private function get_router( DataView $view ): \Tangible\DataView\RequestRouter return $property->getValue( $view ); } + /** + * Render the protected notices output for a DataView's router. + */ + private function render_notices_output( DataView $view ): string { + $router = $this->get_router( $view ); + $method = new \ReflectionMethod( \Tangible\DataView\RequestRouter::class, 'render_notices' ); + $method->setAccessible( true ); + + ob_start(); + $method->invoke( $router ); + return ob_get_clean(); + } + + public function test_notices_config_defaults_to_true(): void { + $config = new DataViewConfig( [ + 'slug' => 'dv_test_notices_default', + 'label' => 'Settings', + 'fields' => [ 'enabled' => 'boolean' ], + ] ); + + $this->assertTrue( $config->notices ); + } + + public function test_notices_config_can_be_disabled(): void { + $config = new DataViewConfig( [ + 'slug' => 'dv_test_notices_off', + 'label' => 'Settings', + 'fields' => [ 'enabled' => 'boolean' ], + 'notices' => false, + ] ); + + $this->assertFalse( $config->notices ); + } + + public function test_render_notices_outputs_when_enabled(): void { + $_GET['updated'] = '1'; + + $view = new DataView( [ + 'slug' => 'dv_test_notices_on_render', + 'label' => 'Settings', + 'mode' => 'singular', + 'storage' => 'option', + 'fields' => [ 'enabled' => 'boolean' ], + ] ); + + $output = $this->render_notices_output( $view ); + unset( $_GET['updated'] ); + + $this->assertStringContainsString( 'notice-success', $output ); + } + + public function test_render_notices_suppressed_when_disabled(): void { + $_GET['updated'] = '1'; + + $view = new DataView( [ + 'slug' => 'dv_test_notices_off_render', + 'label' => 'Settings', + 'mode' => 'singular', + 'storage' => 'option', + 'fields' => [ 'enabled' => 'boolean' ], + 'notices' => false, + ] ); + + $output = $this->render_notices_output( $view ); + unset( $_GET['updated'] ); + + $this->assertSame( '', $output, 'render_notices() must output nothing when notices are disabled' ); + } + public function test_field_type_registry_has_repeater_type(): void { $registry = new FieldTypeRegistry();