From 2930fd1564117530b1505dd0f494c1cf011bb97f Mon Sep 17 00:00:00 2001 From: Peter Wilson Date: Mon, 26 Oct 2026 08:11:11 +1100 Subject: [PATCH 01/33] Increase query limit, per @m. --- src/wp-admin/includes/dashboard.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/wp-admin/includes/dashboard.php b/src/wp-admin/includes/dashboard.php index 165b556f9eb85..a6c093e0b54f8 100644 --- a/src/wp-admin/includes/dashboard.php +++ b/src/wp-admin/includes/dashboard.php @@ -1112,7 +1112,7 @@ function wp_dashboard_on_this_day_get_posts() { $args = array( 'post_type' => 'post', 'post_status' => array( 'publish' ), - 'posts_per_page' => 10, + 'posts_per_page' => 100, 'ignore_sticky_posts' => true, 'orderby' => 'date', 'order' => 'DESC', From c3e69fe2b723571965712b0b8d687daaa85750d2 Mon Sep 17 00:00:00 2001 From: Peter Wilson Date: Mon, 26 Oct 2026 09:09:49 +1100 Subject: [PATCH 02/33] OTD: Markup form. --- src/wp-admin/includes/dashboard.php | 22 ++++++++++++++++++++++ src/wp-admin/includes/schema.php | 1 + 2 files changed, 23 insertions(+) diff --git a/src/wp-admin/includes/dashboard.php b/src/wp-admin/includes/dashboard.php index a6c093e0b54f8..cdffff0e4f2a3 100644 --- a/src/wp-admin/includes/dashboard.php +++ b/src/wp-admin/includes/dashboard.php @@ -1020,6 +1020,10 @@ function wp_dashboard_site_activity() { * @return bool False if no posts were found. True otherwise. */ function wp_dashboard_on_this_day( $posts = null ) { + if ( 'opt-out' === get_option( 'wp_dashboard_show_on_this_day', 'unknown' ) ) { + return false; + } + if ( null === $posts ) { $posts = wp_dashboard_on_this_day_get_posts(); } @@ -1030,6 +1034,24 @@ function wp_dashboard_on_this_day( $posts = null ) { echo '
'; echo '

' . esc_html__( 'Published On This Day' ) . '

'; + + if ( 'opt-in' !== get_option( 'wp_dashboard_show_on_this_day', 'unknown' ) ) { + ?> +
+

+ Would you like to see posts from previous years here? You can change this later in the Screen Options tab. +

+

+ + + +

+
+
+ '; foreach ( $posts as $post ) { diff --git a/src/wp-admin/includes/schema.php b/src/wp-admin/includes/schema.php index d175c65e3649c..d5f8a1affd669 100644 --- a/src/wp-admin/includes/schema.php +++ b/src/wp-admin/includes/schema.php @@ -566,6 +566,7 @@ function populate_options( array $options = array() ) { // 7.2.0 'wp_random_base' => '', + 'wp_dashboard_show_on_this_day' => 'unknown', ); // 3.3.0 From 794f554ded3ca4db4ad813a539a4e519a4ba02f2 Mon Sep 17 00:00:00 2001 From: Peter Wilson Date: Mon, 26 Oct 2026 10:40:14 +1100 Subject: [PATCH 03/33] On This Day opt-in. --- src/js/_enqueues/admin/postbox.js | 61 +++++++++++++++++++++++ src/wp-admin/admin-ajax.php | 1 + src/wp-admin/css/dashboard.css | 8 +-- src/wp-admin/includes/ajax-actions.php | 14 ++++++ src/wp-admin/includes/class-wp-screen.php | 13 +++++ src/wp-admin/includes/dashboard.php | 35 +++++++------ src/wp-admin/includes/schema.php | 1 - src/wp-admin/includes/screen.php | 13 +++++ 8 files changed, 126 insertions(+), 20 deletions(-) diff --git a/src/js/_enqueues/admin/postbox.js b/src/js/_enqueues/admin/postbox.js index fec46d3f6d909..a26a5caf0cdce 100644 --- a/src/js/_enqueues/admin/postbox.js +++ b/src/js/_enqueues/admin/postbox.js @@ -355,6 +355,16 @@ postboxes.setMetaBoxReordering( enabled ); postboxes.save_meta_box_reordering_state( enabled ); } ); + + $( '#on-this-day-posts-form form button' ).on( 'click', function( event ) { + event.preventDefault(); + var enabled = '1' === $( this ).prop( 'value' ); + + postboxes.setOnThisDayDisplay( enabled ); + postboxes.save_on_this_day_display( enabled ); + } ); + + $() }, /** @@ -495,6 +505,31 @@ } }, + /** + * Enables or disables the On This Day display in the activity widget. + * + * @since 7.2.0 + * + * @param {boolean} enabled Whether On This Day should be enabled. + * @return {void} + */ + setOnThisDayDisplay: function( enabled ) { + var $onThisDayList = $( '#on-this-day-posts-list' ) + $onThisDayForm = $( '#on-this-day-posts-form'), + $onThisDayScreenOption = $( '#wp-on-this-day-toggle' ); + + this.onThisDayEnabled = enabled; + $onThisDayForm.hide(); // Always hidden once a choice is made + + if ( enabled ) { + $onThisDayList.show(); + $onThisDayScreenOption.prop( 'checked', true ); + return; + } + $onThisDayList.hide(); + $onThisDayScreenOption.prop( 'checked', false ); + }, + /** * Saves the state of the postboxes to the server. * @@ -560,6 +595,32 @@ ); }, + /** + * Saves the On This Day setting to the server. + * + * @since 7.2.0 + * + * @param {boolean} enabled Whether On This Day activity is enabled. + * @return {void} + */ + save_on_this_day_display: function( enabled ) { + $.post( + ajaxurl, + { + action: 'on-this-day-display', + enabled: enabled ? 1 : 0, + screenoptionnonce: $( '#screenoptionnonce' ).val() + }, + function() { + wp.a11y.speak( + enabled ? + __( 'On This Day display is enabled.' ) : + __( 'On This Day display is disabled.' ) + ); + } + ); + }, + /** * Saves the order of the postboxes to the server. * diff --git a/src/wp-admin/admin-ajax.php b/src/wp-admin/admin-ajax.php index e62c285fa356c..0472c3bea45b8 100644 --- a/src/wp-admin/admin-ajax.php +++ b/src/wp-admin/admin-ajax.php @@ -79,6 +79,7 @@ 'closed-postboxes', 'hidden-columns', 'meta-box-reordering', + 'on-this-day-display', 'update-welcome-panel', 'menu-get-metabox', 'wp-link-ajax', diff --git a/src/wp-admin/css/dashboard.css b/src/wp-admin/css/dashboard.css index a79960cb39419..01bef455a0d2d 100644 --- a/src/wp-admin/css/dashboard.css +++ b/src/wp-admin/css/dashboard.css @@ -931,13 +931,13 @@ body #dashboard-widgets .postbox form .submit { #future-posts ul, #published-posts ul, -#on-this-day-posts ul { +#on-this-day-posts-list ul { margin: 8px -12px 0 -12px; } #future-posts li, #published-posts li, -#on-this-day-posts li { +#on-this-day-posts-list li { display: grid; grid-template-columns: clamp(160px, calc(2vw + 140px), 200px) auto; column-gap: 10px; @@ -947,11 +947,11 @@ body #dashboard-widgets .postbox form .submit { #future-posts li:nth-child(odd), #published-posts li:nth-child(odd), -#on-this-day-posts li:nth-child(odd) { +#on-this-day-posts-list li:nth-child(odd) { background-color: #f6f7f7; } -#on-this-day-posts .wp-on-this-day-post-author { +#on-this-day-posts-list .wp-on-this-day-post-author { color: #646970; } diff --git a/src/wp-admin/includes/ajax-actions.php b/src/wp-admin/includes/ajax-actions.php index 03fefef0689ff..61f5dae530497 100644 --- a/src/wp-admin/includes/ajax-actions.php +++ b/src/wp-admin/includes/ajax-actions.php @@ -1818,6 +1818,20 @@ function wp_ajax_meta_box_reordering() { wp_die( 1 ); } +/** + * Handles the On This Day display setting via Ajax. + * + * @since 7.2.0 + */ +function wp_ajax_on_this_day_display() { + check_ajax_referer( 'screen-options-nonce', 'screenoptionnonce' ); + + $enabled = isset( $_POST['enabled'] ) && '1' === (string) $_POST['enabled']; + update_user_option( get_current_user_id(), 'dashboard_show_on_this_day', $enabled ? 'opt-in' : 'opt-out' ); + + wp_die( 1 ); +} + /** * Handles updating whether to display the welcome panel via AJAX. * diff --git a/src/wp-admin/includes/class-wp-screen.php b/src/wp-admin/includes/class-wp-screen.php index 0008130355efb..59ccac861bb83 100644 --- a/src/wp-admin/includes/class-wp-screen.php +++ b/src/wp-admin/includes/class-wp-screen.php @@ -1011,6 +1011,7 @@ public function show_screen_options() { if ( $has_meta_boxes ) { $additional_settings .= $this->get_meta_box_reordering_option(); + $additional_settings .= $this->get_on_this_day_option(); } if ( 'post' === $this->base ) { @@ -1184,6 +1185,18 @@ private function get_meta_box_reordering_option() { . ''; } + /** + * Gets the option to enable or disable meta box reordering. + * + * @since 7.2.0 + * + * @return string Meta box reordering option markup. + */ + private function get_on_this_day_option() { + return '' + . ''; + } + /** * Checks whether the screen has meta boxes. * diff --git a/src/wp-admin/includes/dashboard.php b/src/wp-admin/includes/dashboard.php index cdffff0e4f2a3..dc0616e86c8fc 100644 --- a/src/wp-admin/includes/dashboard.php +++ b/src/wp-admin/includes/dashboard.php @@ -1020,8 +1020,15 @@ function wp_dashboard_site_activity() { * @return bool False if no posts were found. True otherwise. */ function wp_dashboard_on_this_day( $posts = null ) { - if ( 'opt-out' === get_option( 'wp_dashboard_show_on_this_day', 'unknown' ) ) { - return false; + if ( 'opt-out' === get_user_option( 'dashboard_show_on_this_day' ) ) { + $display_list = false; + $display_form = false; + } elseif ( 'opt-in' === get_user_option( 'dashboard_show_on_this_day' ) ) { + $display_list = true; + $display_form = false; + } else { + $display_list = false; + $display_form = true; } if ( null === $posts ) { @@ -1032,27 +1039,25 @@ function wp_dashboard_on_this_day( $posts = null ) { return false; } - echo '
'; - echo '

' . esc_html__( 'Published On This Day' ) . '

'; - - if ( 'opt-in' !== get_option( 'wp_dashboard_show_on_this_day', 'unknown' ) ) { - ?> + ?> +
+

Would you like to see posts from previous years here? You can change this later in the Screen Options tab.

- - - + + +

-
- - echo '
    '; +
    +

    +
      + '', - 'wp_dashboard_show_on_this_day' => 'unknown', ); // 3.3.0 diff --git a/src/wp-admin/includes/screen.php b/src/wp-admin/includes/screen.php index 3781aacdd1fa6..4ee0aabd82813 100644 --- a/src/wp-admin/includes/screen.php +++ b/src/wp-admin/includes/screen.php @@ -207,6 +207,19 @@ function wp_is_meta_box_reordering_enabled() { return false === $setting ? true : 'disabled' !== $setting; } +/** + * Determines whether On This Day display is enabled. + * + * @since 7.2.0 + * + * @return bool Whether meta box reordering is enabled. + */ +function wp_is_on_this_day_enabled() { + $setting = get_user_option( 'dashboard_show_on_this_day' ); + + return 'opt-in' === $setting; +} + /** * Register and configure an admin screen option * From b40624794a60e0f4b30e8454e118834a2578c536 Mon Sep 17 00:00:00 2001 From: Peter Wilson Date: Mon, 26 Oct 2026 10:48:11 +1100 Subject: [PATCH 04/33] No need for setting to be stored on object. --- src/js/_enqueues/admin/postbox.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/js/_enqueues/admin/postbox.js b/src/js/_enqueues/admin/postbox.js index a26a5caf0cdce..1d6ffcee044e9 100644 --- a/src/js/_enqueues/admin/postbox.js +++ b/src/js/_enqueues/admin/postbox.js @@ -518,9 +518,7 @@ $onThisDayForm = $( '#on-this-day-posts-form'), $onThisDayScreenOption = $( '#wp-on-this-day-toggle' ); - this.onThisDayEnabled = enabled; - $onThisDayForm.hide(); // Always hidden once a choice is made - + $onThisDayForm.hide(); // Always hidden once a choice is made. if ( enabled ) { $onThisDayList.show(); $onThisDayScreenOption.prop( 'checked', true ); From 7fae9f77f5d472d69cf95c13110997f36417f838 Mon Sep 17 00:00:00 2001 From: Peter Wilson Date: Mon, 26 Oct 2026 10:53:54 +1100 Subject: [PATCH 05/33] Did not mean to delete this. --- src/js/_enqueues/admin/postbox.js | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/js/_enqueues/admin/postbox.js b/src/js/_enqueues/admin/postbox.js index 1d6ffcee044e9..9bb4d63d107e0 100644 --- a/src/js/_enqueues/admin/postbox.js +++ b/src/js/_enqueues/admin/postbox.js @@ -356,15 +356,20 @@ postboxes.save_meta_box_reordering_state( enabled ); } ); - $( '#on-this-day-posts-form form button' ).on( 'click', function( event ) { + $( '.wp-on-this-day-toggle' ).on( 'click.postboxes', function() { + var enabled = $( this ).prop( 'checked' ); + + postboxes.setOnThisDayDisplay( enabled ); + postboxes.save_on_this_day_display( enabled ); + } ); + + $( '#on-this-day-posts-form form button' ).on( 'click.postboxes', function( event ) { event.preventDefault(); var enabled = '1' === $( this ).prop( 'value' ); postboxes.setOnThisDayDisplay( enabled ); postboxes.save_on_this_day_display( enabled ); } ); - - $() }, /** From 0fea4822e220e09f738d8dec82931f13d6bbce74 Mon Sep 17 00:00:00 2001 From: Peter Wilson Date: Mon, 26 Oct 2026 10:59:22 +1100 Subject: [PATCH 06/33] Prefix all the things. --- src/js/_enqueues/admin/postbox.js | 6 +++--- src/wp-admin/css/dashboard.css | 8 ++++---- src/wp-admin/includes/dashboard.php | 4 ++-- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/js/_enqueues/admin/postbox.js b/src/js/_enqueues/admin/postbox.js index 9bb4d63d107e0..1c1dc393b8e60 100644 --- a/src/js/_enqueues/admin/postbox.js +++ b/src/js/_enqueues/admin/postbox.js @@ -363,7 +363,7 @@ postboxes.save_on_this_day_display( enabled ); } ); - $( '#on-this-day-posts-form form button' ).on( 'click.postboxes', function( event ) { + $( '#wp-on-this-day-posts-form form button' ).on( 'click.postboxes', function( event ) { event.preventDefault(); var enabled = '1' === $( this ).prop( 'value' ); @@ -519,8 +519,8 @@ * @return {void} */ setOnThisDayDisplay: function( enabled ) { - var $onThisDayList = $( '#on-this-day-posts-list' ) - $onThisDayForm = $( '#on-this-day-posts-form'), + var $onThisDayList = $( '#wp-on-this-day-posts-list' ) + $onThisDayForm = $( '#wp-on-this-day-posts-form'), $onThisDayScreenOption = $( '#wp-on-this-day-toggle' ); $onThisDayForm.hide(); // Always hidden once a choice is made. diff --git a/src/wp-admin/css/dashboard.css b/src/wp-admin/css/dashboard.css index 01bef455a0d2d..6121fd5032084 100644 --- a/src/wp-admin/css/dashboard.css +++ b/src/wp-admin/css/dashboard.css @@ -931,13 +931,13 @@ body #dashboard-widgets .postbox form .submit { #future-posts ul, #published-posts ul, -#on-this-day-posts-list ul { +#wp-on-this-day-posts-list ul { margin: 8px -12px 0 -12px; } #future-posts li, #published-posts li, -#on-this-day-posts-list li { +#wp-on-this-day-posts-list li { display: grid; grid-template-columns: clamp(160px, calc(2vw + 140px), 200px) auto; column-gap: 10px; @@ -947,11 +947,11 @@ body #dashboard-widgets .postbox form .submit { #future-posts li:nth-child(odd), #published-posts li:nth-child(odd), -#on-this-day-posts-list li:nth-child(odd) { +#wp-on-this-day-posts-list li:nth-child(odd) { background-color: #f6f7f7; } -#on-this-day-posts-list .wp-on-this-day-post-author { +#wp-on-this-day-posts-list .wp-on-this-day-post-author { color: #646970; } diff --git a/src/wp-admin/includes/dashboard.php b/src/wp-admin/includes/dashboard.php index dc0616e86c8fc..25208476083e7 100644 --- a/src/wp-admin/includes/dashboard.php +++ b/src/wp-admin/includes/dashboard.php @@ -1040,7 +1040,7 @@ function wp_dashboard_on_this_day( $posts = null ) { } ?> -
      +

      @@ -1054,7 +1054,7 @@ function wp_dashboard_on_this_day( $posts = null ) {

      -
      +

        Date: Mon, 26 Oct 2026 11:03:34 +1100 Subject: [PATCH 07/33] missing comma. --- src/js/_enqueues/admin/postbox.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/js/_enqueues/admin/postbox.js b/src/js/_enqueues/admin/postbox.js index 1c1dc393b8e60..ea2ac3fe81e06 100644 --- a/src/js/_enqueues/admin/postbox.js +++ b/src/js/_enqueues/admin/postbox.js @@ -519,7 +519,7 @@ * @return {void} */ setOnThisDayDisplay: function( enabled ) { - var $onThisDayList = $( '#wp-on-this-day-posts-list' ) + var $onThisDayList = $( '#wp-on-this-day-posts-list' ), $onThisDayForm = $( '#wp-on-this-day-posts-form'), $onThisDayScreenOption = $( '#wp-on-this-day-toggle' ); From 7f5b55000b7db0c89294cc62a60d27106fcd70b3 Mon Sep 17 00:00:00 2001 From: Peter Wilson Date: Mon, 26 Oct 2026 11:44:07 +1100 Subject: [PATCH 08/33] Update test for number of posts queried by OTD widget. --- .../tests/admin/wpDashboardSiteActivity.php | 23 +++++++++++++------ 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/tests/phpunit/tests/admin/wpDashboardSiteActivity.php b/tests/phpunit/tests/admin/wpDashboardSiteActivity.php index 9d5f673afc7d6..819bc441e38b7 100644 --- a/tests/phpunit/tests/admin/wpDashboardSiteActivity.php +++ b/tests/phpunit/tests/admin/wpDashboardSiteActivity.php @@ -143,19 +143,28 @@ public function test_query_returns_only_published_posts_from_this_day_in_previou * * @covers ::wp_dashboard_on_this_day_get_posts */ - public function test_query_defaults_to_ten_posts_and_limit_is_filterable() { + public function test_query_defaults_to_one_hundred_posts_and_limit_is_filterable() { for ( $years_ago = 1; $years_ago <= 11; $years_ago++ ) { $this->create_matching_post( self::$user_id, 'Anniversary post ' . $years_ago, $years_ago ); } - $this->assertCount( 10, wp_dashboard_on_this_day_get_posts() ); + $capture = null; + add_filter( + 'wp_dashboard_on_this_day_query_args', + static function ( $query_args ) use ( &$capture ) { + $capture = $query_args['posts_per_page']; + return $query_args; + }, + 20 // Run after other filters have run. + ); + + wp_dashboard_on_this_day_get_posts(); + $this->assertCount( 11, wp_dashboard_on_this_day_get_posts(), 'Unfiltered OTD Widget should return all posts.' ); + $this->assertSame( 100, $capture, 'OTD Widget query should default to 100 posts per page.' ); add_filter( 'wp_dashboard_on_this_day_query_args', array( $this, 'filter_on_this_day_query_limit' ) ); - try { - $this->assertCount( 3, wp_dashboard_on_this_day_get_posts() ); - } finally { - remove_filter( 'wp_dashboard_on_this_day_query_args', array( $this, 'filter_on_this_day_query_limit' ) ); - } + $this->assertCount( 3, wp_dashboard_on_this_day_get_posts(), 'Filtered OTD Widget should return three posts.' ); + $this->assertSame( 3, $capture, 'Filter OTD Widget query should be limited to three posts per page.' ); } /** From 47747705191569d25fbd5f92ca08e3123a164ea7 Mon Sep 17 00:00:00 2001 From: Peter Wilson Date: Mon, 26 Oct 2026 11:46:17 +1100 Subject: [PATCH 09/33] Remove uneeded teardown. --- .../tests/admin/wpDashboardSiteActivity.php | 44 +++++++------------ 1 file changed, 17 insertions(+), 27 deletions(-) diff --git a/tests/phpunit/tests/admin/wpDashboardSiteActivity.php b/tests/phpunit/tests/admin/wpDashboardSiteActivity.php index 819bc441e38b7..580352c0fb3b5 100644 --- a/tests/phpunit/tests/admin/wpDashboardSiteActivity.php +++ b/tests/phpunit/tests/admin/wpDashboardSiteActivity.php @@ -214,13 +214,7 @@ public function test_section_hides_untitled_excerpt_for_unreadable_posts() { ); add_filter( 'wp_dashboard_on_this_day_query_args', array( $this, 'filter_on_this_day_query_private_posts' ) ); - try { - ob_start(); - wp_dashboard_on_this_day(); - $output = ob_get_clean(); - } finally { - remove_filter( 'wp_dashboard_on_this_day_query_args', array( $this, 'filter_on_this_day_query_private_posts' ) ); - } + $output = get_echo( 'wp_dashboard_on_this_day' ); $this->assertStringContainsString( '(no title)', $output ); $this->assertStringNotContainsString( 'Unreadable private anniversary memory.', $output ); @@ -360,31 +354,27 @@ public function test_recent_comments_widget_renders_empty_state_when_user_has_no public function test_recent_comments_widget_remains_registered_after_comments_are_deleted() { add_filter( 'pre_http_request', array( $this, 'mock_http_request_failure' ) ); - try { - wp_dashboard_setup(); - $this->assertArrayNotHasKey( 'dashboard_recent_comments', $GLOBALS['wp_meta_boxes']['dashboard']['normal']['core'] ); + wp_dashboard_setup(); + $this->assertArrayNotHasKey( 'dashboard_recent_comments', $GLOBALS['wp_meta_boxes']['dashboard']['normal']['core'] ); - unset( $GLOBALS['wp_meta_boxes']['dashboard'] ); + unset( $GLOBALS['wp_meta_boxes']['dashboard'] ); - $post_id = self::factory()->post->create(); - $comment_id = self::factory()->comment->create( array( 'comment_post_ID' => $post_id ) ); + $post_id = self::factory()->post->create(); + $comment_id = self::factory()->comment->create( array( 'comment_post_ID' => $post_id ) ); - wp_dashboard_setup(); - $this->assertArrayHasKey( 'dashboard_recent_comments', $GLOBALS['wp_meta_boxes']['dashboard']['normal']['core'] ); - $this->assertSame( - 'wp_dashboard_recent_comments_widget', - $GLOBALS['wp_meta_boxes']['dashboard']['normal']['core']['dashboard_recent_comments']['callback'] - ); - $this->assertSame( 1, get_option( 'dashboard_recent_comments_widget_registered' ) ); + wp_dashboard_setup(); + $this->assertArrayHasKey( 'dashboard_recent_comments', $GLOBALS['wp_meta_boxes']['dashboard']['normal']['core'] ); + $this->assertSame( + 'wp_dashboard_recent_comments_widget', + $GLOBALS['wp_meta_boxes']['dashboard']['normal']['core']['dashboard_recent_comments']['callback'] + ); + $this->assertSame( 1, get_option( 'dashboard_recent_comments_widget_registered' ) ); - wp_delete_comment( $comment_id, true ); - unset( $GLOBALS['wp_meta_boxes']['dashboard'] ); + wp_delete_comment( $comment_id, true ); + unset( $GLOBALS['wp_meta_boxes']['dashboard'] ); - wp_dashboard_setup(); - $this->assertArrayHasKey( 'dashboard_recent_comments', $GLOBALS['wp_meta_boxes']['dashboard']['normal']['core'] ); - } finally { - remove_filter( 'pre_http_request', array( $this, 'mock_http_request_failure' ) ); - } + wp_dashboard_setup(); + $this->assertArrayHasKey( 'dashboard_recent_comments', $GLOBALS['wp_meta_boxes']['dashboard']['normal']['core'] ); } /** From 132390e48daf365454b9fd7330e8fde21f5932ba Mon Sep 17 00:00:00 2001 From: Peter Wilson Date: Mon, 26 Oct 2026 11:51:33 +1100 Subject: [PATCH 10/33] Use get_echo helper for test suite. --- .../tests/admin/wpDashboardSiteActivity.php | 30 +++++-------------- 1 file changed, 7 insertions(+), 23 deletions(-) diff --git a/tests/phpunit/tests/admin/wpDashboardSiteActivity.php b/tests/phpunit/tests/admin/wpDashboardSiteActivity.php index 580352c0fb3b5..4163057313ee1 100644 --- a/tests/phpunit/tests/admin/wpDashboardSiteActivity.php +++ b/tests/phpunit/tests/admin/wpDashboardSiteActivity.php @@ -236,9 +236,7 @@ public function test_section_hides_untitled_excerpt_for_password_protected_posts ) ); - ob_start(); - wp_dashboard_on_this_day(); - $output = ob_get_clean(); + $output = get_echo( 'wp_dashboard_on_this_day' ); $this->assertStringContainsString( '(no title)', $output ); $this->assertStringNotContainsString( 'Password-protected anniversary memory.', $output ); @@ -250,9 +248,7 @@ public function test_section_hides_untitled_excerpt_for_password_protected_posts * @covers ::wp_dashboard_site_activity */ public function test_activity_renders_no_activity_message_without_posts() { - ob_start(); - wp_dashboard_site_activity(); - $output = ob_get_clean(); + $output = get_echo( 'wp_dashboard_site_activity' ); $this->assertStringContainsString( 'No activity yet!', $output ); $this->assertStringNotContainsString( 'Published On This Day', $output ); @@ -274,9 +270,7 @@ public function test_activity_does_not_repeat_on_this_day_posts_in_recently_publ ) ); - ob_start(); - wp_dashboard_site_activity(); - $output = ob_get_clean(); + $output = get_echo( 'wp_dashboard_site_activity' ); $this->assertStringContainsString( 'Published On This Day', $output ); $this->assertSame( 1, preg_match( '/
        /s', $output, $recent_posts ) ); @@ -304,17 +298,9 @@ public function test_recent_comments_are_rendered_only_in_their_dedicated_widget ) ); - ob_start(); - wp_dashboard_site_activity(); - $activity_output = ob_get_clean(); - - ob_start(); - wp_dashboard_recent_comments_widget(); - $comments_output = ob_get_clean(); - - ob_start(); - wp_dashboard_recent_comments(); - $direct_output = ob_get_clean(); + $activity_output = get_echo( 'wp_dashboard_site_activity' ); + $comments_output = get_echo( 'wp_dashboard_recent_comments_widget' ); + $direct_output = get_echo( 'wp_dashboard_recent_comments' ); $this->assertStringNotContainsString( 'A comment in its own widget.', $activity_output ); $this->assertStringContainsString( 'No activity yet!', $activity_output ); @@ -338,9 +324,7 @@ public function test_recent_comments_widget_renders_empty_state_when_user_has_no ); wp_set_current_user( self::factory()->user->create( array( 'role' => 'subscriber' ) ) ); - ob_start(); - wp_dashboard_recent_comments_widget(); - $output = ob_get_clean(); + $output = get_echo( 'wp_dashboard_recent_comments_widget' ); $this->assertStringContainsString( 'No comments to show.', $output ); } From 08c4372bd744a260a5d412fc1a38e024eb69716e Mon Sep 17 00:00:00 2001 From: Peter Wilson Date: Mon, 26 Oct 2026 11:55:47 +1100 Subject: [PATCH 11/33] Add messages to tests with multiple assertions. --- .../tests/admin/wpDashboardSiteActivity.php | 57 ++++++++++--------- 1 file changed, 29 insertions(+), 28 deletions(-) diff --git a/tests/phpunit/tests/admin/wpDashboardSiteActivity.php b/tests/phpunit/tests/admin/wpDashboardSiteActivity.php index 4163057313ee1..8eb6a9b7e4951 100644 --- a/tests/phpunit/tests/admin/wpDashboardSiteActivity.php +++ b/tests/phpunit/tests/admin/wpDashboardSiteActivity.php @@ -186,15 +186,15 @@ public function test_section_renders_posts_from_all_authors_and_untitled_excerpt $result = wp_dashboard_on_this_day(); $output = ob_get_clean(); - $this->assertTrue( $result ); - $this->assertStringContainsString( 'Published On This Day', $output ); - $this->assertStringContainsString( 'A note from me', $output ); - $this->assertStringNotContainsString( 'by Current Writer', $output ); - $this->assertStringContainsString( 'A note from someone else', $output ); - $this->assertStringContainsString( 'by Guest Writer', $output ); - $this->assertStringContainsString( '(no title)', $output ); - $this->assertStringContainsString( 'word15', $output ); - $this->assertStringNotContainsString( 'word16', $output ); + $this->assertTrue( $result, 'OTD Widget should return true when posts are rendered.' ); + $this->assertStringContainsString( 'Published On This Day', $output, 'OTD Widget should render the section heading.' ); + $this->assertStringContainsString( 'A note from me', $output, 'OTD Widget should render posts by the current user.' ); + $this->assertStringNotContainsString( 'by Current Writer', $output, 'OTD Widget should not show a byline for the current user.' ); + $this->assertStringContainsString( 'A note from someone else', $output, 'OTD Widget should render posts by other users.' ); + $this->assertStringContainsString( 'by Guest Writer', $output, 'OTD Widget should show a byline for other users.' ); + $this->assertStringContainsString( '(no title)', $output, 'OTD Widget should render a placeholder for untitled posts.' ); + $this->assertStringContainsString( 'word15', $output, 'OTD Widget should include the first fifteen words of the excerpt for untitled posts.' ); + $this->assertStringNotContainsString( 'word16', $output, 'OTD Widget should trim the excerpt for untitled posts to fifteen words.' ); } /** @@ -216,8 +216,8 @@ public function test_section_hides_untitled_excerpt_for_unreadable_posts() { add_filter( 'wp_dashboard_on_this_day_query_args', array( $this, 'filter_on_this_day_query_private_posts' ) ); $output = get_echo( 'wp_dashboard_on_this_day' ); - $this->assertStringContainsString( '(no title)', $output ); - $this->assertStringNotContainsString( 'Unreadable private anniversary memory.', $output ); + $this->assertStringContainsString( '(no title)', $output, 'OTD Widget should render a placeholder for untitled unreadable posts.' ); + $this->assertStringNotContainsString( 'Unreadable private anniversary memory.', $output, 'OTD Widget should not render the excerpt of unreadable posts.' ); } /** @@ -238,8 +238,8 @@ public function test_section_hides_untitled_excerpt_for_password_protected_posts $output = get_echo( 'wp_dashboard_on_this_day' ); - $this->assertStringContainsString( '(no title)', $output ); - $this->assertStringNotContainsString( 'Password-protected anniversary memory.', $output ); + $this->assertStringContainsString( '(no title)', $output, 'OTD Widget should render a placeholder for untitled password-protected posts.' ); + $this->assertStringNotContainsString( 'Password-protected anniversary memory.', $output, 'OTD Widget should not render the excerpt of password-protected posts.' ); } /** @@ -250,8 +250,8 @@ public function test_section_hides_untitled_excerpt_for_password_protected_posts public function test_activity_renders_no_activity_message_without_posts() { $output = get_echo( 'wp_dashboard_site_activity' ); - $this->assertStringContainsString( 'No activity yet!', $output ); - $this->assertStringNotContainsString( 'Published On This Day', $output ); + $this->assertStringContainsString( 'No activity yet!', $output, 'Activity Widget should render the empty state when there are no posts.' ); + $this->assertStringNotContainsString( 'Published On This Day', $output, 'Activity Widget should not render the OTD section when there are no posts.' ); } /** @@ -272,9 +272,9 @@ public function test_activity_does_not_repeat_on_this_day_posts_in_recently_publ $output = get_echo( 'wp_dashboard_site_activity' ); - $this->assertStringContainsString( 'Published On This Day', $output ); - $this->assertSame( 1, preg_match( '/
        /s', $output, $recent_posts ) ); - $this->assertStringNotContainsString( 'One anniversary mention', $recent_posts[0] ); + $this->assertStringContainsString( 'Published On This Day', $output, 'Activity Widget should render the OTD section.' ); + $this->assertSame( 1, preg_match( '/
        /s', $output, $recent_posts ), 'Activity Widget should render the recently published section.' ); + $this->assertStringNotContainsString( 'One anniversary mention', $recent_posts[0], 'Recently published section should not repeat posts shown in the OTD section.' ); } /** @@ -302,11 +302,11 @@ public function test_recent_comments_are_rendered_only_in_their_dedicated_widget $comments_output = get_echo( 'wp_dashboard_recent_comments_widget' ); $direct_output = get_echo( 'wp_dashboard_recent_comments' ); - $this->assertStringNotContainsString( 'A comment in its own widget.', $activity_output ); - $this->assertStringContainsString( 'No activity yet!', $activity_output ); - $this->assertStringContainsString( 'A comment in its own widget.', $comments_output ); - $this->assertStringNotContainsString( '

        Recent Comments

        ', $comments_output ); - $this->assertStringContainsString( '

        Recent Comments

        ', $direct_output ); + $this->assertStringNotContainsString( 'A comment in its own widget.', $activity_output, 'Activity Widget should not render recent comments.' ); + $this->assertStringContainsString( 'No activity yet!', $activity_output, 'Activity Widget should render the empty state when comments are the only activity.' ); + $this->assertStringContainsString( 'A comment in its own widget.', $comments_output, 'Recent Comments Widget should render recent comments.' ); + $this->assertStringNotContainsString( '

        Recent Comments

        ', $comments_output, 'Recent Comments Widget should not render the section heading.' ); + $this->assertStringContainsString( '

        Recent Comments

        ', $direct_output, 'Recent comments rendered directly should include the section heading.' ); } /** @@ -339,7 +339,7 @@ public function test_recent_comments_widget_remains_registered_after_comments_ar add_filter( 'pre_http_request', array( $this, 'mock_http_request_failure' ) ); wp_dashboard_setup(); - $this->assertArrayNotHasKey( 'dashboard_recent_comments', $GLOBALS['wp_meta_boxes']['dashboard']['normal']['core'] ); + $this->assertArrayNotHasKey( 'dashboard_recent_comments', $GLOBALS['wp_meta_boxes']['dashboard']['normal']['core'], 'Recent Comments Widget should not be registered before any comments are received.' ); unset( $GLOBALS['wp_meta_boxes']['dashboard'] ); @@ -347,18 +347,19 @@ public function test_recent_comments_widget_remains_registered_after_comments_ar $comment_id = self::factory()->comment->create( array( 'comment_post_ID' => $post_id ) ); wp_dashboard_setup(); - $this->assertArrayHasKey( 'dashboard_recent_comments', $GLOBALS['wp_meta_boxes']['dashboard']['normal']['core'] ); + $this->assertArrayHasKey( 'dashboard_recent_comments', $GLOBALS['wp_meta_boxes']['dashboard']['normal']['core'], 'Recent Comments Widget should be registered once a comment is received.' ); $this->assertSame( 'wp_dashboard_recent_comments_widget', - $GLOBALS['wp_meta_boxes']['dashboard']['normal']['core']['dashboard_recent_comments']['callback'] + $GLOBALS['wp_meta_boxes']['dashboard']['normal']['core']['dashboard_recent_comments']['callback'], + 'Recent Comments Widget should use the dedicated widget callback.' ); - $this->assertSame( 1, get_option( 'dashboard_recent_comments_widget_registered' ) ); + $this->assertSame( 1, get_option( 'dashboard_recent_comments_widget_registered' ), 'Recent Comments Widget registration should be stored in an option.' ); wp_delete_comment( $comment_id, true ); unset( $GLOBALS['wp_meta_boxes']['dashboard'] ); wp_dashboard_setup(); - $this->assertArrayHasKey( 'dashboard_recent_comments', $GLOBALS['wp_meta_boxes']['dashboard']['normal']['core'] ); + $this->assertArrayHasKey( 'dashboard_recent_comments', $GLOBALS['wp_meta_boxes']['dashboard']['normal']['core'], 'Recent Comments Widget should remain registered after all comments are deleted.' ); } /** From 1948059949ab67118ce00f3080eb5cb2cbdf0e4d Mon Sep 17 00:00:00 2001 From: Peter Wilson Date: Mon, 26 Oct 2026 11:58:27 +1100 Subject: [PATCH 12/33] Clarify OTD tests are for OTD. --- .../phpunit/tests/admin/wpDashboardSiteActivity.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/phpunit/tests/admin/wpDashboardSiteActivity.php b/tests/phpunit/tests/admin/wpDashboardSiteActivity.php index 8eb6a9b7e4951..c6f351a368a6a 100644 --- a/tests/phpunit/tests/admin/wpDashboardSiteActivity.php +++ b/tests/phpunit/tests/admin/wpDashboardSiteActivity.php @@ -84,7 +84,7 @@ private function create_matching_post( $author_id, $title = 'A memory from last * * @covers ::_wp_dashboard_on_this_day_date_query_clause */ - public function test_date_query_includes_february_29_on_february_28_in_non_leap_year() { + public function test_otd_date_query_includes_february_29_on_february_28_in_non_leap_year() { $date = new DateTimeImmutable( '2023-02-28 12:00:00', wp_timezone() ); $this->assertSame( @@ -108,7 +108,7 @@ public function test_date_query_includes_february_29_on_february_28_in_non_leap_ * * @covers ::wp_dashboard_on_this_day_get_posts */ - public function test_query_returns_only_published_posts_from_this_day_in_previous_years() { + public function test_otd_query_returns_only_published_posts_from_this_day_in_previous_years() { $matching_id = $this->create_matching_post( self::$user_id ); $this->create_matching_post( self::$user_id, 'A draft memory', 2, array( 'post_status' => 'draft' ) ); @@ -143,7 +143,7 @@ public function test_query_returns_only_published_posts_from_this_day_in_previou * * @covers ::wp_dashboard_on_this_day_get_posts */ - public function test_query_defaults_to_one_hundred_posts_and_limit_is_filterable() { + public function test_otd_query_defaults_to_one_hundred_posts_and_limit_is_filterable() { for ( $years_ago = 1; $years_ago <= 11; $years_ago++ ) { $this->create_matching_post( self::$user_id, 'Anniversary post ' . $years_ago, $years_ago ); } @@ -172,7 +172,7 @@ static function ( $query_args ) use ( &$capture ) { * * @covers ::wp_dashboard_on_this_day */ - public function test_section_renders_posts_from_all_authors_and_untitled_excerpts() { + public function test_otd_renders_posts_from_all_authors_and_untitled_excerpts() { $this->create_matching_post( self::$user_id, 'A note from me' ); $this->create_matching_post( self::$other_user_id, 'A note from someone else', 2 ); $this->create_matching_post( @@ -202,7 +202,7 @@ public function test_section_renders_posts_from_all_authors_and_untitled_excerpt * * @covers ::wp_dashboard_on_this_day */ - public function test_section_hides_untitled_excerpt_for_unreadable_posts() { + public function test_otd_hides_untitled_excerpt_for_unreadable_posts() { $this->create_matching_post( self::$other_user_id, '', @@ -225,7 +225,7 @@ public function test_section_hides_untitled_excerpt_for_unreadable_posts() { * * @covers ::wp_dashboard_on_this_day */ - public function test_section_hides_untitled_excerpt_for_password_protected_posts() { + public function test_otd_hides_untitled_excerpt_for_password_protected_posts() { $this->create_matching_post( self::$user_id, '', From f0420de79f03759faeae8dcd87b4ea6b11541c0e Mon Sep 17 00:00:00 2001 From: Peter Wilson Date: Mon, 26 Oct 2026 12:13:37 +1100 Subject: [PATCH 13/33] Ensure OTD posts are hidden before opt in. --- .../tests/admin/wpDashboardSiteActivity.php | 58 +++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/tests/phpunit/tests/admin/wpDashboardSiteActivity.php b/tests/phpunit/tests/admin/wpDashboardSiteActivity.php index c6f351a368a6a..934097824a018 100644 --- a/tests/phpunit/tests/admin/wpDashboardSiteActivity.php +++ b/tests/phpunit/tests/admin/wpDashboardSiteActivity.php @@ -242,6 +242,31 @@ public function test_otd_hides_untitled_excerpt_for_password_protected_posts() { $this->assertStringNotContainsString( 'Password-protected anniversary memory.', $output, 'OTD Widget should not render the excerpt of password-protected posts.' ); } + /** + * @ticket 65116 + * + * @covers ::wp_dashboard_on_this_day + */ + public function test_otd_activity_is_hidden_before_opt_in() { + $this->create_matching_post( self::$user_id, 'A note from me' ); + $this->assertFalse( get_user_option( 'dashboard_show_on_this_day' ), 'User option should default to false' ); + + $otd_activity = get_echo( 'wp_dashboard_on_this_day' ); + + $inline_styles = $this->get_inline_styles_by_id( $otd_activity ); + + $this->assertArrayHasKey( 'wp-on-this-day-posts-list', $inline_styles, 'OTD Widget should render the posts list element.' ); + $this->assertArrayHasKey( 'display', $inline_styles['wp-on-this-day-posts-list'], 'OTD Widget posts list should set an inline display property before the user opts in.' ); + $this->assertSame( 'none', $inline_styles['wp-on-this-day-posts-list']['display'], 'OTD Widget posts list should be hidden before the user opts in.' ); + + $this->assertArrayHasKey( 'wp-on-this-day-posts-form', $inline_styles, 'OTD Widget should render the opt-in form element.' ); + $this->assertSame( + 'block', + $inline_styles['wp-on-this-day-posts-form']['display'] ?? 'block', + 'OTD Widget opt-in form should not set an inline display property other than block before the user opts in.' + ); + } + /** * @ticket 65116 * @@ -362,6 +387,39 @@ public function test_recent_comments_widget_remains_registered_after_comments_ar $this->assertArrayHasKey( 'dashboard_recent_comments', $GLOBALS['wp_meta_boxes']['dashboard']['normal']['core'], 'Recent Comments Widget should remain registered after all comments are deleted.' ); } + /** + * Gets the inline style declarations of each element with an ID. + * + * @param string $html HTML fragment to parse. + * @return array[] Declarations keyed by element ID, then by lowercase property name. + */ + private function get_inline_styles_by_id( $html ) { + $processor = WP_HTML_Processor::create_fragment( $html ); + $inline_styles = array(); + + while ( $processor->next_tag() ) { + $id = $processor->get_attribute( 'id' ); + if ( ! is_string( $id ) ) { + continue; + } + + $declarations = array(); + foreach ( explode( ';', (string) $processor->get_attribute( 'style' ) ) as $declaration ) { + if ( ! str_contains( $declaration, ':' ) ) { + continue; + } + + list( $property, $value ) = explode( ':', $declaration, 2 ); + + $declarations[ strtolower( trim( $property ) ) ] = trim( $value ); + } + + $inline_styles[ $id ] = $declarations; + } + + return $inline_styles; + } + /** * Mocks an HTTP request failure during dashboard setup. * From e0836e0ecb3990906876326caf379fb55efdb253 Mon Sep 17 00:00:00 2001 From: Peter Wilson Date: Mon, 26 Oct 2026 12:27:38 +1100 Subject: [PATCH 14/33] Do not render form after opt-in or -out. --- src/wp-admin/includes/dashboard.php | 32 ++++++++++++++++------------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/src/wp-admin/includes/dashboard.php b/src/wp-admin/includes/dashboard.php index 25208476083e7..9ebe7e4a0957d 100644 --- a/src/wp-admin/includes/dashboard.php +++ b/src/wp-admin/includes/dashboard.php @@ -1039,21 +1039,25 @@ function wp_dashboard_on_this_day( $posts = null ) { return false; } - ?> -
        -

        -
        -

        - Would you like to see posts from previous years here? You can change this later in the Screen Options tab. -

        -

        - - - -

        -
        -
        + if ( ! str_starts_with( get_user_option( 'dashboard_show_on_this_day' ), 'opt-' ) ) : + ?> +
        +

        +
        +

        + Would you like to see posts from previous years here? You can change this later in the Screen Options tab. +

        +

        + + + +

        +
        +
        +

          From 6a87b41e16790bd3196434425338049cfde8d1f4 Mon Sep 17 00:00:00 2001 From: Peter Wilson Date: Mon, 26 Oct 2026 12:27:59 +1100 Subject: [PATCH 15/33] Test nothing displayed after opt-out. HTML renders for list. --- .../tests/admin/wpDashboardSiteActivity.php | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/tests/phpunit/tests/admin/wpDashboardSiteActivity.php b/tests/phpunit/tests/admin/wpDashboardSiteActivity.php index 934097824a018..49afde0f761ad 100644 --- a/tests/phpunit/tests/admin/wpDashboardSiteActivity.php +++ b/tests/phpunit/tests/admin/wpDashboardSiteActivity.php @@ -267,6 +267,25 @@ public function test_otd_activity_is_hidden_before_opt_in() { ); } + /** + * @ticket 65116 + * + * @covers ::wp_dashboard_on_this_day + */ + public function test_otd_activity_is_hidden_after_opt_out() { + update_user_option( self::$user_id, 'dashboard_show_on_this_day', 'opt-out' ); + $this->create_matching_post( self::$user_id, 'A note from me' ); + + $otd_activity = get_echo( 'wp_dashboard_on_this_day' ); + $inline_styles = $this->get_inline_styles_by_id( $otd_activity ); + + $this->assertArrayHasKey( 'wp-on-this-day-posts-list', $inline_styles, 'OTD Widget should render the posts list element.' ); + $this->assertArrayHasKey( 'display', $inline_styles['wp-on-this-day-posts-list'], 'OTD Widget posts list should set an inline display property after the user opts out.' ); + $this->assertSame( 'none', $inline_styles['wp-on-this-day-posts-list']['display'], 'OTD Widget posts list should be hidden after the user opts out.' ); + + $this->assertArrayNotHasKey( 'wp-on-this-day-posts-form', $inline_styles, 'OTD Widget should not render the opt-in form after the user opts out.' ); + } + /** * @ticket 65116 * From 2a2f6db76ed36ca45ce7f2f4e338d7482ce7073a Mon Sep 17 00:00:00 2001 From: Peter Wilson Date: Mon, 26 Oct 2026 12:30:04 +1100 Subject: [PATCH 16/33] Ensure list displays after user has opted in. --- .../tests/admin/wpDashboardSiteActivity.php | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/tests/phpunit/tests/admin/wpDashboardSiteActivity.php b/tests/phpunit/tests/admin/wpDashboardSiteActivity.php index 49afde0f761ad..2e5b1c86326d6 100644 --- a/tests/phpunit/tests/admin/wpDashboardSiteActivity.php +++ b/tests/phpunit/tests/admin/wpDashboardSiteActivity.php @@ -286,6 +286,28 @@ public function test_otd_activity_is_hidden_after_opt_out() { $this->assertArrayNotHasKey( 'wp-on-this-day-posts-form', $inline_styles, 'OTD Widget should not render the opt-in form after the user opts out.' ); } + /** + * @ticket 65116 + * + * @covers ::wp_dashboard_on_this_day + */ + public function test_otd_activity_is_displayed_after_opt_in() { + update_user_option( self::$user_id, 'dashboard_show_on_this_day', 'opt-in' ); + $this->create_matching_post( self::$user_id, 'A note from me' ); + + $otd_activity = get_echo( 'wp_dashboard_on_this_day' ); + $inline_styles = $this->get_inline_styles_by_id( $otd_activity ); + + $this->assertArrayNotHasKey( 'wp-on-this-day-posts-form', $inline_styles, 'OTD Widget should not render the opt-in form after the user opts in.' ); + + $this->assertArrayHasKey( 'wp-on-this-day-posts-list', $inline_styles, 'OTD Widget should render the posts list element.' ); + $this->assertSame( + 'block', + $inline_styles['wp-on-this-day-posts-list']['display'] ?? 'block', + 'OTD Widget posts list should not set an inline display property other than block after the user opts in.' + ); + } + /** * @ticket 65116 * From f1712ba8ebb3a639ae86c479bad5a2690894a990 Mon Sep 17 00:00:00 2001 From: Peter Wilson Date: Mon, 26 Oct 2026 12:35:36 +1100 Subject: [PATCH 17/33] Add summary to OTD tests. --- .../tests/admin/wpDashboardSiteActivity.php | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/tests/phpunit/tests/admin/wpDashboardSiteActivity.php b/tests/phpunit/tests/admin/wpDashboardSiteActivity.php index 2e5b1c86326d6..3f0f5450a47e8 100644 --- a/tests/phpunit/tests/admin/wpDashboardSiteActivity.php +++ b/tests/phpunit/tests/admin/wpDashboardSiteActivity.php @@ -80,6 +80,8 @@ private function create_matching_post( $author_id, $title = 'A memory from last } /** + * Tests that February 28 includes leap day posts in non-leap years. + * * @ticket 65116 * * @covers ::_wp_dashboard_on_this_day_date_query_clause @@ -104,6 +106,8 @@ public function test_otd_date_query_includes_february_29_on_february_28_in_non_l } /** + * Tests that only published posts from this day in prior years are queried. + * * @ticket 65116 * * @covers ::wp_dashboard_on_this_day_get_posts @@ -139,6 +143,8 @@ public function test_otd_query_returns_only_published_posts_from_this_day_in_pre } /** + * Tests that the On This Day query limit defaults to 100 and is filterable. + * * @ticket 65116 * * @covers ::wp_dashboard_on_this_day_get_posts @@ -168,6 +174,8 @@ static function ( $query_args ) use ( &$capture ) { } /** + * Tests that posts by all authors and untitled post excerpts are rendered. + * * @ticket 65116 * * @covers ::wp_dashboard_on_this_day @@ -198,6 +206,8 @@ public function test_otd_renders_posts_from_all_authors_and_untitled_excerpts() } /** + * Tests that excerpts of unreadable untitled posts are not rendered. + * * @ticket 65116 * * @covers ::wp_dashboard_on_this_day @@ -221,6 +231,8 @@ public function test_otd_hides_untitled_excerpt_for_unreadable_posts() { } /** + * Tests that excerpts of password-protected untitled posts are not rendered. + * * @ticket 65116 * * @covers ::wp_dashboard_on_this_day @@ -243,6 +255,8 @@ public function test_otd_hides_untitled_excerpt_for_password_protected_posts() { } /** + * Tests that the opt-in form is shown and the list hidden before opting in. + * * @ticket 65116 * * @covers ::wp_dashboard_on_this_day @@ -268,6 +282,8 @@ public function test_otd_activity_is_hidden_before_opt_in() { } /** + * Tests that the form is not rendered and the list is hidden after opt-out. + * * @ticket 65116 * * @covers ::wp_dashboard_on_this_day @@ -287,6 +303,8 @@ public function test_otd_activity_is_hidden_after_opt_out() { } /** + * Tests that the form is not rendered and the list is shown after opt-in. + * * @ticket 65116 * * @covers ::wp_dashboard_on_this_day @@ -321,6 +339,8 @@ public function test_activity_renders_no_activity_message_without_posts() { } /** + * Tests that On This Day posts are not repeated in Recently Published. + * * @ticket 65116 * * @covers ::wp_dashboard_site_activity From d604837465b2c57c6ee4726646f95a95661fb60e Mon Sep 17 00:00:00 2001 From: Peter Wilson <519727+peterwilsoncc@users.noreply.github.com> Date: Mon, 26 Oct 2026 14:09:46 +1100 Subject: [PATCH 18/33] i18n fixes. Co-authored-by: Weston Ruter --- src/wp-admin/includes/dashboard.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/wp-admin/includes/dashboard.php b/src/wp-admin/includes/dashboard.php index 9ebe7e4a0957d..d73cd8e313f01 100644 --- a/src/wp-admin/includes/dashboard.php +++ b/src/wp-admin/includes/dashboard.php @@ -1042,15 +1042,15 @@ function wp_dashboard_on_this_day( $posts = null ) { if ( ! str_starts_with( get_user_option( 'dashboard_show_on_this_day' ), 'opt-' ) ) : ?>
          -

          +

          - Would you like to see posts from previous years here? You can change this later in the Screen Options tab. +

          - - + +

          From 0f3f61075bd84df0f7e83abb2953881734657ce6 Mon Sep 17 00:00:00 2001 From: Peter Wilson <519727+peterwilsoncc@users.noreply.github.com> Date: Mon, 26 Oct 2026 14:13:03 +1100 Subject: [PATCH 19/33] Type fixes. Co-authored-by: Weston Ruter --- src/wp-admin/includes/ajax-actions.php | 6 ++++-- src/wp-admin/includes/class-wp-screen.php | 2 +- src/wp-admin/includes/screen.php | 2 +- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/wp-admin/includes/ajax-actions.php b/src/wp-admin/includes/ajax-actions.php index 61f5dae530497..4a335d374ecd6 100644 --- a/src/wp-admin/includes/ajax-actions.php +++ b/src/wp-admin/includes/ajax-actions.php @@ -1822,11 +1822,13 @@ function wp_ajax_meta_box_reordering() { * Handles the On This Day display setting via Ajax. * * @since 7.2.0 + * + * @return never */ -function wp_ajax_on_this_day_display() { +function wp_ajax_on_this_day_display(): void { check_ajax_referer( 'screen-options-nonce', 'screenoptionnonce' ); - $enabled = isset( $_POST['enabled'] ) && '1' === (string) $_POST['enabled']; + $enabled = isset( $_POST['enabled'] ) && is_string( $_POST['enabled'] ) && '1' === $_POST['enabled']; update_user_option( get_current_user_id(), 'dashboard_show_on_this_day', $enabled ? 'opt-in' : 'opt-out' ); wp_die( 1 ); diff --git a/src/wp-admin/includes/class-wp-screen.php b/src/wp-admin/includes/class-wp-screen.php index 59ccac861bb83..be3ba6ae556bf 100644 --- a/src/wp-admin/includes/class-wp-screen.php +++ b/src/wp-admin/includes/class-wp-screen.php @@ -1192,7 +1192,7 @@ private function get_meta_box_reordering_option() { * * @return string Meta box reordering option markup. */ - private function get_on_this_day_option() { + private function get_on_this_day_option(): string { return '' . ''; } diff --git a/src/wp-admin/includes/screen.php b/src/wp-admin/includes/screen.php index 4ee0aabd82813..f529d361ad1e4 100644 --- a/src/wp-admin/includes/screen.php +++ b/src/wp-admin/includes/screen.php @@ -214,7 +214,7 @@ function wp_is_meta_box_reordering_enabled() { * * @return bool Whether meta box reordering is enabled. */ -function wp_is_on_this_day_enabled() { +function wp_is_on_this_day_enabled(): bool { $setting = get_user_option( 'dashboard_show_on_this_day' ); return 'opt-in' === $setting; From b5174d2f74b69a87419f9323df46a52c23457b19 Mon Sep 17 00:00:00 2001 From: Peter Wilson Date: Mon, 26 Oct 2026 14:16:14 +1100 Subject: [PATCH 20/33] Trolling? --- src/wp-admin/includes/dashboard.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/wp-admin/includes/dashboard.php b/src/wp-admin/includes/dashboard.php index d73cd8e313f01..18df7ca1b83b6 100644 --- a/src/wp-admin/includes/dashboard.php +++ b/src/wp-admin/includes/dashboard.php @@ -1045,7 +1045,7 @@ function wp_dashboard_on_this_day( $posts = null ) {

          -

          From 32f28b2e1c262a770ff52b2584860f0e8027c000 Mon Sep 17 00:00:00 2001 From: Peter Wilson <519727+peterwilsoncc@users.noreply.github.com> Date: Mon, 26 Oct 2026 14:34:48 +1100 Subject: [PATCH 21/33] Smarten up toggling display. Co-authored-by: Weston Ruter --- src/js/_enqueues/admin/postbox.js | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/js/_enqueues/admin/postbox.js b/src/js/_enqueues/admin/postbox.js index ea2ac3fe81e06..1d0dc48c2a243 100644 --- a/src/js/_enqueues/admin/postbox.js +++ b/src/js/_enqueues/admin/postbox.js @@ -524,13 +524,8 @@ $onThisDayScreenOption = $( '#wp-on-this-day-toggle' ); $onThisDayForm.hide(); // Always hidden once a choice is made. - if ( enabled ) { - $onThisDayList.show(); - $onThisDayScreenOption.prop( 'checked', true ); - return; - } - $onThisDayList.hide(); - $onThisDayScreenOption.prop( 'checked', false ); + $onThisDayList.toggle( enabled ); + $onThisDayScreenOption.prop( 'checked', enabled ); }, /** From c9fd3e8c757a435eabe1aa6549bf6e7510d90632 Mon Sep 17 00:00:00 2001 From: Peter Wilson Date: Mon, 26 Oct 2026 14:44:35 +1100 Subject: [PATCH 22/33] Comment: explain what else means. --- src/wp-admin/includes/dashboard.php | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/wp-admin/includes/dashboard.php b/src/wp-admin/includes/dashboard.php index 18df7ca1b83b6..c9c67f5a85900 100644 --- a/src/wp-admin/includes/dashboard.php +++ b/src/wp-admin/includes/dashboard.php @@ -1027,6 +1027,14 @@ function wp_dashboard_on_this_day( $posts = null ) { $display_list = true; $display_form = false; } else { + /* + * User has not yet chosen to opt-in or -out. + * + * As On This Day features can be distressing in some circumstances, + * users are prompted to opt-in or -out of the feature prior to the + * list of posts being displayed. If they have done neither a form is + * displayed prompting them to make a choice. + */ $display_list = false; $display_form = true; } From ec27f55cd5ed863c61f71e9992b4493bb176fc2e Mon Sep 17 00:00:00 2001 From: Peter Wilson Date: Mon, 26 Oct 2026 14:50:33 +1100 Subject: [PATCH 23/33] Use variable to determine whether to display the form, remove pointless ternary. --- src/wp-admin/includes/dashboard.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/wp-admin/includes/dashboard.php b/src/wp-admin/includes/dashboard.php index c9c67f5a85900..d8eb9e6717deb 100644 --- a/src/wp-admin/includes/dashboard.php +++ b/src/wp-admin/includes/dashboard.php @@ -1047,9 +1047,9 @@ function wp_dashboard_on_this_day( $posts = null ) { return false; } - if ( ! str_starts_with( get_user_option( 'dashboard_show_on_this_day' ), 'opt-' ) ) : + if ( $display_form ) : ?> -

          +

          From b58426ecbf8d46489e6a374c8ec52179f914edf8 Mon Sep 17 00:00:00 2001 From: Peter Wilson Date: Mon, 26 Oct 2026 15:05:40 +1100 Subject: [PATCH 24/33] Typo: Filter -> filtered. --- tests/phpunit/tests/admin/wpDashboardSiteActivity.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/phpunit/tests/admin/wpDashboardSiteActivity.php b/tests/phpunit/tests/admin/wpDashboardSiteActivity.php index 3f0f5450a47e8..72aa012bffe3e 100644 --- a/tests/phpunit/tests/admin/wpDashboardSiteActivity.php +++ b/tests/phpunit/tests/admin/wpDashboardSiteActivity.php @@ -170,7 +170,7 @@ static function ( $query_args ) use ( &$capture ) { add_filter( 'wp_dashboard_on_this_day_query_args', array( $this, 'filter_on_this_day_query_limit' ) ); $this->assertCount( 3, wp_dashboard_on_this_day_get_posts(), 'Filtered OTD Widget should return three posts.' ); - $this->assertSame( 3, $capture, 'Filter OTD Widget query should be limited to three posts per page.' ); + $this->assertSame( 3, $capture, 'Filtered OTD Widget query should be limited to three posts per page.' ); } /** From 014c60539f33a11080241e89d19bf3f7a27fd6e4 Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Mon, 26 Oct 2026 13:29:13 -0700 Subject: [PATCH 25/33] Use mock action and move method to closure --- .../tests/admin/wpDashboardSiteActivity.php | 38 +++++++++---------- 1 file changed, 18 insertions(+), 20 deletions(-) diff --git a/tests/phpunit/tests/admin/wpDashboardSiteActivity.php b/tests/phpunit/tests/admin/wpDashboardSiteActivity.php index 72aa012bffe3e..a6eba086e135c 100644 --- a/tests/phpunit/tests/admin/wpDashboardSiteActivity.php +++ b/tests/phpunit/tests/admin/wpDashboardSiteActivity.php @@ -154,23 +154,33 @@ public function test_otd_query_defaults_to_one_hundred_posts_and_limit_is_filter $this->create_matching_post( self::$user_id, 'Anniversary post ' . $years_ago, $years_ago ); } - $capture = null; + $mock = new MockAction(); add_filter( 'wp_dashboard_on_this_day_query_args', - static function ( $query_args ) use ( &$capture ) { - $capture = $query_args['posts_per_page']; - return $query_args; - }, + array( $mock, 'filter' ), 20 // Run after other filters have run. ); wp_dashboard_on_this_day_get_posts(); $this->assertCount( 11, wp_dashboard_on_this_day_get_posts(), 'Unfiltered OTD Widget should return all posts.' ); - $this->assertSame( 100, $capture, 'OTD Widget query should default to 100 posts per page.' ); + $this->assertSame( 2, $mock->get_call_count() ); + $query_args = $mock->get_args()[ $mock->get_call_count() - 1 ][0]; + $this->assertIsArray( $query_args ); + $this->assertArrayHasKey( 'posts_per_page', $query_args ); + $this->assertSame( 100, $query_args['posts_per_page'], 'OTD Widget query should default to 100 posts per page.' ); - add_filter( 'wp_dashboard_on_this_day_query_args', array( $this, 'filter_on_this_day_query_limit' ) ); + add_filter( + 'wp_dashboard_on_this_day_query_args', + static function ( array $args ) { + $args['posts_per_page'] = 3; + return $args; + } + ); $this->assertCount( 3, wp_dashboard_on_this_day_get_posts(), 'Filtered OTD Widget should return three posts.' ); - $this->assertSame( 3, $capture, 'Filtered OTD Widget query should be limited to three posts per page.' ); + $query_args = $mock->get_args()[ $mock->get_call_count() - 1 ][0]; + $this->assertIsArray( $query_args ); + $this->assertArrayHasKey( 'posts_per_page', $query_args ); + $this->assertSame( 3, $query_args['posts_per_page'], 'Filtered OTD Widget query should be limited to three posts per page.' ); } /** @@ -490,18 +500,6 @@ public function mock_http_request_failure() { return new WP_Error( 'http_request_failed' ); } - /** - * Sets the On This Day query limit to three. - * - * @param array $args Query arguments. - * @return array Filtered query arguments. - */ - public function filter_on_this_day_query_limit( $args ) { - $args['posts_per_page'] = 3; - - return $args; - } - /** * Filters the On This Day query to include private posts. * From 2decea5f9e0464a19e2b0f87325aa7f24b335a4d Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Mon, 26 Oct 2026 13:34:58 -0700 Subject: [PATCH 26/33] Fix static analysis in test_otd_renders_posts_from_all_authors_and_untitled_excerpts --- tests/phpunit/tests/admin/wpDashboardSiteActivity.php | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/phpunit/tests/admin/wpDashboardSiteActivity.php b/tests/phpunit/tests/admin/wpDashboardSiteActivity.php index a6eba086e135c..19ca0e69dad7f 100644 --- a/tests/phpunit/tests/admin/wpDashboardSiteActivity.php +++ b/tests/phpunit/tests/admin/wpDashboardSiteActivity.php @@ -203,6 +203,7 @@ public function test_otd_renders_posts_from_all_authors_and_untitled_excerpts() ob_start(); $result = wp_dashboard_on_this_day(); $output = ob_get_clean(); + $this->assertIsString( $output ); $this->assertTrue( $result, 'OTD Widget should return true when posts are rendered.' ); $this->assertStringContainsString( 'Published On This Day', $output, 'OTD Widget should render the section heading.' ); From f02d84e049293bed167ca9c6b4f3cd9c0cb7fa89 Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Mon, 26 Oct 2026 13:35:31 -0700 Subject: [PATCH 27/33] Improve static analysis in test_recent_comments_widget_remains_registered_after_comments_are_deleted --- .../tests/admin/wpDashboardSiteActivity.php | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/tests/phpunit/tests/admin/wpDashboardSiteActivity.php b/tests/phpunit/tests/admin/wpDashboardSiteActivity.php index 19ca0e69dad7f..2398eebc88cb0 100644 --- a/tests/phpunit/tests/admin/wpDashboardSiteActivity.php +++ b/tests/phpunit/tests/admin/wpDashboardSiteActivity.php @@ -431,32 +431,37 @@ public function test_recent_comments_widget_renders_empty_state_when_user_has_no * * @covers ::_wp_dashboard_has_received_comments * @covers ::wp_dashboard_setup + * + * @global array{ dashboard: array{ normal: array{ core: array }, ... }, ... } $wp_meta_boxes */ public function test_recent_comments_widget_remains_registered_after_comments_are_deleted() { + global $wp_meta_boxes; add_filter( 'pre_http_request', array( $this, 'mock_http_request_failure' ) ); wp_dashboard_setup(); - $this->assertArrayNotHasKey( 'dashboard_recent_comments', $GLOBALS['wp_meta_boxes']['dashboard']['normal']['core'], 'Recent Comments Widget should not be registered before any comments are received.' ); + $this->assertArrayHasKey( 'dashboard', $wp_meta_boxes ); + $this->assertArrayNotHasKey( 'dashboard_recent_comments', $wp_meta_boxes['dashboard']['normal']['core'], 'Recent Comments Widget should not be registered before any comments are received.' ); - unset( $GLOBALS['wp_meta_boxes']['dashboard'] ); + unset( $wp_meta_boxes['dashboard'] ); $post_id = self::factory()->post->create(); $comment_id = self::factory()->comment->create( array( 'comment_post_ID' => $post_id ) ); wp_dashboard_setup(); - $this->assertArrayHasKey( 'dashboard_recent_comments', $GLOBALS['wp_meta_boxes']['dashboard']['normal']['core'], 'Recent Comments Widget should be registered once a comment is received.' ); + $this->assertArrayHasKey( 'dashboard', $wp_meta_boxes ); + $this->assertArrayHasKey( 'dashboard_recent_comments', $wp_meta_boxes['dashboard']['normal']['core'], 'Recent Comments Widget should be registered once a comment is received.' ); $this->assertSame( 'wp_dashboard_recent_comments_widget', - $GLOBALS['wp_meta_boxes']['dashboard']['normal']['core']['dashboard_recent_comments']['callback'], + $wp_meta_boxes['dashboard']['normal']['core']['dashboard_recent_comments']['callback'], 'Recent Comments Widget should use the dedicated widget callback.' ); $this->assertSame( 1, get_option( 'dashboard_recent_comments_widget_registered' ), 'Recent Comments Widget registration should be stored in an option.' ); wp_delete_comment( $comment_id, true ); - unset( $GLOBALS['wp_meta_boxes']['dashboard'] ); + unset( $wp_meta_boxes['dashboard'] ); wp_dashboard_setup(); - $this->assertArrayHasKey( 'dashboard_recent_comments', $GLOBALS['wp_meta_boxes']['dashboard']['normal']['core'], 'Recent Comments Widget should remain registered after all comments are deleted.' ); + $this->assertArrayHasKey( 'dashboard_recent_comments', $wp_meta_boxes['dashboard']['normal']['core'], 'Recent Comments Widget should remain registered after all comments are deleted.' ); } /** From 22692a0a554a08435504b1ada742e8a5b2717bc4 Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Mon, 26 Oct 2026 13:38:00 -0700 Subject: [PATCH 28/33] Improve static analysis of get_inline_styles_by_id test helper --- tests/phpunit/tests/admin/wpDashboardSiteActivity.php | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/tests/phpunit/tests/admin/wpDashboardSiteActivity.php b/tests/phpunit/tests/admin/wpDashboardSiteActivity.php index 2398eebc88cb0..ad77fd4ff6940 100644 --- a/tests/phpunit/tests/admin/wpDashboardSiteActivity.php +++ b/tests/phpunit/tests/admin/wpDashboardSiteActivity.php @@ -468,10 +468,11 @@ public function test_recent_comments_widget_remains_registered_after_comments_ar * Gets the inline style declarations of each element with an ID. * * @param string $html HTML fragment to parse. - * @return array[] Declarations keyed by element ID, then by lowercase property name. + * @return array> Declarations keyed by element ID, then by lowercase property name. */ - private function get_inline_styles_by_id( $html ) { - $processor = WP_HTML_Processor::create_fragment( $html ); + private function get_inline_styles_by_id( string $html ): array { + $processor = WP_HTML_Processor::create_fragment( $html ); + $this->assertInstanceOf( WP_HTML_Processor::class, $processor ); $inline_styles = array(); while ( $processor->next_tag() ) { From a622593f61d5b236243514f05b451b42d2499530 Mon Sep 17 00:00:00 2001 From: Peter Wilson Date: Tue, 27 Oct 2026 10:02:38 +1100 Subject: [PATCH 29/33] Fix styles: extra line at bottom of widget. --- src/js/_enqueues/admin/postbox.js | 6 ++++-- src/wp-admin/css/dashboard.css | 10 +++++++-- src/wp-admin/includes/dashboard.php | 32 ++++++++++++++--------------- 3 files changed, 28 insertions(+), 20 deletions(-) diff --git a/src/js/_enqueues/admin/postbox.js b/src/js/_enqueues/admin/postbox.js index 1d0dc48c2a243..3b103a44a0441 100644 --- a/src/js/_enqueues/admin/postbox.js +++ b/src/js/_enqueues/admin/postbox.js @@ -363,7 +363,7 @@ postboxes.save_on_this_day_display( enabled ); } ); - $( '#wp-on-this-day-posts-form form button' ).on( 'click.postboxes', function( event ) { + $( '#wp-on-this-day-posts-form button' ).on( 'click.postboxes', function( event ) { event.preventDefault(); var enabled = '1' === $( this ).prop( 'value' ); @@ -519,11 +519,13 @@ * @return {void} */ setOnThisDayDisplay: function( enabled ) { - var $onThisDayList = $( '#wp-on-this-day-posts-list' ), + var $onThisDayWidget = $( '#wp-on-this-day-posts' ), + $onThisDayList = $( '#wp-on-this-day-posts-list' ), $onThisDayForm = $( '#wp-on-this-day-posts-form'), $onThisDayScreenOption = $( '#wp-on-this-day-toggle' ); $onThisDayForm.hide(); // Always hidden once a choice is made. + $onThisDayWidget.toggle( enabled ).toggleClass( 'hidden', ! enabled ); $onThisDayList.toggle( enabled ); $onThisDayScreenOption.prop( 'checked', enabled ); }, diff --git a/src/wp-admin/css/dashboard.css b/src/wp-admin/css/dashboard.css index 6121fd5032084..3f172a6e8d44e 100644 --- a/src/wp-admin/css/dashboard.css +++ b/src/wp-admin/css/dashboard.css @@ -931,13 +931,13 @@ body #dashboard-widgets .postbox form .submit { #future-posts ul, #published-posts ul, -#wp-on-this-day-posts-list ul { +#wp-on-this-day-posts ul { margin: 8px -12px 0 -12px; } #future-posts li, #published-posts li, -#wp-on-this-day-posts-list li { +#wp-on-this-day-posts li { display: grid; grid-template-columns: clamp(160px, calc(2vw + 140px), 200px) auto; column-gap: 10px; @@ -966,6 +966,12 @@ body #dashboard-widgets .postbox form .submit { margin-bottom: 0; } +/* The last visible block, ignoring any hidden blocks that follow it. */ +.activity-block:nth-last-child(1 of :not(.hidden)) { + border-bottom: none; + margin-bottom: 0; +} + .activity-block .subsubsub li { color: #646970; } diff --git a/src/wp-admin/includes/dashboard.php b/src/wp-admin/includes/dashboard.php index d8eb9e6717deb..c68edb52425a7 100644 --- a/src/wp-admin/includes/dashboard.php +++ b/src/wp-admin/includes/dashboard.php @@ -1047,28 +1047,28 @@ function wp_dashboard_on_this_day( $posts = null ) { return false; } + ?> +

          +

          + -
          -

          - -

          - -

          -

          - - - -

          - -
          +
          +

          + +

          +

          + + + +

          +
          -
          -

          -
            +
              > Date: Tue, 27 Oct 2026 10:13:01 +1100 Subject: [PATCH 30/33] Modify LI styles to account for those in common.css. --- src/wp-admin/css/dashboard.css | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/wp-admin/css/dashboard.css b/src/wp-admin/css/dashboard.css index 3f172a6e8d44e..347f8b1cd7cc9 100644 --- a/src/wp-admin/css/dashboard.css +++ b/src/wp-admin/css/dashboard.css @@ -942,7 +942,8 @@ body #dashboard-widgets .postbox form .submit { grid-template-columns: clamp(160px, calc(2vw + 140px), 200px) auto; column-gap: 10px; color: #646970; - padding: 4px 12px; + padding: 7px 12px; + margin-bottom: 0; } #future-posts li:nth-child(odd), From fbbfa23677ba5626f72bbb214a32e28e90d5d4fe Mon Sep 17 00:00:00 2001 From: Peter Wilson Date: Tue, 27 Oct 2026 10:19:10 +1100 Subject: [PATCH 31/33] Account for hide-if-no-js if there is no JS. --- src/wp-admin/css/dashboard.css | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/wp-admin/css/dashboard.css b/src/wp-admin/css/dashboard.css index 347f8b1cd7cc9..547251e8959b1 100644 --- a/src/wp-admin/css/dashboard.css +++ b/src/wp-admin/css/dashboard.css @@ -968,7 +968,8 @@ body #dashboard-widgets .postbox form .submit { } /* The last visible block, ignoring any hidden blocks that follow it. */ -.activity-block:nth-last-child(1 of :not(.hidden)) { +.activity-block:nth-last-child(1 of :not(.hidden)), +.no-js .activity-block:nth-last-child(1 of :not(.hidden, .hide-if-no-js)) { border-bottom: none; margin-bottom: 0; } From 2dad3b67bca81d48c8bebeea6ce9c9f2f50a9d08 Mon Sep 17 00:00:00 2001 From: Peter Wilson Date: Tue, 27 Oct 2026 10:47:19 +1100 Subject: [PATCH 32/33] Pre-flight filter for OTD activity. --- src/wp-admin/includes/dashboard.php | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/wp-admin/includes/dashboard.php b/src/wp-admin/includes/dashboard.php index c68edb52425a7..ee2a6ef5e6fc5 100644 --- a/src/wp-admin/includes/dashboard.php +++ b/src/wp-admin/includes/dashboard.php @@ -1145,6 +1145,25 @@ function wp_dashboard_on_this_day( $posts = null ) { * @return WP_Post[] Array of posts ordered by newest first. */ function wp_dashboard_on_this_day_get_posts() { + /** + * Filters whether to short-circuit performing the query for the On This Day dashboard query. + * + * Filter to allow the bypassing of the SQL query for the On This Day section of the activity + * widget. + * + * Return null for the default query to run or an array of post IDs to override the built in + * database query. Returning an empty array will have the effect of turning the feature off. + * + * @since 7.2.0 + * + * @param WP_Post[]|null $preflight Null to continue, array of post objects to bypass query. + */ + $preflight = apply_filters( 'pre_wp_dashboard_on_this_day_get_posts', null ); + + if ( is_array( $preflight ) ) { + return $preflight; + } + $today = current_datetime(); $year = (int) $today->format( 'Y' ); From d990e65ddc8ec076f47cbd2bcd7dade46bc67568 Mon Sep 17 00:00:00 2001 From: Peter Wilson Date: Tue, 27 Oct 2026 11:16:30 +1100 Subject: [PATCH 33/33] Test for hidden class rather than display style. --- .../tests/admin/wpDashboardSiteActivity.php | 71 ++++++++----------- 1 file changed, 29 insertions(+), 42 deletions(-) diff --git a/tests/phpunit/tests/admin/wpDashboardSiteActivity.php b/tests/phpunit/tests/admin/wpDashboardSiteActivity.php index ad77fd4ff6940..8941b9d45c263 100644 --- a/tests/phpunit/tests/admin/wpDashboardSiteActivity.php +++ b/tests/phpunit/tests/admin/wpDashboardSiteActivity.php @@ -277,19 +277,16 @@ public function test_otd_activity_is_hidden_before_opt_in() { $this->assertFalse( get_user_option( 'dashboard_show_on_this_day' ), 'User option should default to false' ); $otd_activity = get_echo( 'wp_dashboard_on_this_day' ); + $hidden = $this->get_hidden_class_by_id( $otd_activity ); - $inline_styles = $this->get_inline_styles_by_id( $otd_activity ); + $this->assertArrayHasKey( 'wp-on-this-day-posts', $hidden, 'OTD Widget should render the section container.' ); + $this->assertFalse( $hidden['wp-on-this-day-posts'], 'OTD Widget section container should not be hidden before the user opts in.' ); - $this->assertArrayHasKey( 'wp-on-this-day-posts-list', $inline_styles, 'OTD Widget should render the posts list element.' ); - $this->assertArrayHasKey( 'display', $inline_styles['wp-on-this-day-posts-list'], 'OTD Widget posts list should set an inline display property before the user opts in.' ); - $this->assertSame( 'none', $inline_styles['wp-on-this-day-posts-list']['display'], 'OTD Widget posts list should be hidden before the user opts in.' ); + $this->assertArrayHasKey( 'wp-on-this-day-posts-list', $hidden, 'OTD Widget should render the posts list element.' ); + $this->assertTrue( $hidden['wp-on-this-day-posts-list'], 'OTD Widget posts list should be hidden before the user opts in.' ); - $this->assertArrayHasKey( 'wp-on-this-day-posts-form', $inline_styles, 'OTD Widget should render the opt-in form element.' ); - $this->assertSame( - 'block', - $inline_styles['wp-on-this-day-posts-form']['display'] ?? 'block', - 'OTD Widget opt-in form should not set an inline display property other than block before the user opts in.' - ); + $this->assertArrayHasKey( 'wp-on-this-day-posts-form', $hidden, 'OTD Widget should render the opt-in form element.' ); + $this->assertFalse( $hidden['wp-on-this-day-posts-form'], 'OTD Widget opt-in form should not be hidden before the user opts in.' ); } /** @@ -303,14 +300,16 @@ public function test_otd_activity_is_hidden_after_opt_out() { update_user_option( self::$user_id, 'dashboard_show_on_this_day', 'opt-out' ); $this->create_matching_post( self::$user_id, 'A note from me' ); - $otd_activity = get_echo( 'wp_dashboard_on_this_day' ); - $inline_styles = $this->get_inline_styles_by_id( $otd_activity ); + $otd_activity = get_echo( 'wp_dashboard_on_this_day' ); + $hidden = $this->get_hidden_class_by_id( $otd_activity ); + + $this->assertArrayHasKey( 'wp-on-this-day-posts', $hidden, 'OTD Widget should render the section container.' ); + $this->assertTrue( $hidden['wp-on-this-day-posts'], 'OTD Widget section container should be hidden after the user opts out.' ); - $this->assertArrayHasKey( 'wp-on-this-day-posts-list', $inline_styles, 'OTD Widget should render the posts list element.' ); - $this->assertArrayHasKey( 'display', $inline_styles['wp-on-this-day-posts-list'], 'OTD Widget posts list should set an inline display property after the user opts out.' ); - $this->assertSame( 'none', $inline_styles['wp-on-this-day-posts-list']['display'], 'OTD Widget posts list should be hidden after the user opts out.' ); + $this->assertArrayHasKey( 'wp-on-this-day-posts-list', $hidden, 'OTD Widget should render the posts list element.' ); + $this->assertTrue( $hidden['wp-on-this-day-posts-list'], 'OTD Widget posts list should be hidden after the user opts out.' ); - $this->assertArrayNotHasKey( 'wp-on-this-day-posts-form', $inline_styles, 'OTD Widget should not render the opt-in form after the user opts out.' ); + $this->assertArrayNotHasKey( 'wp-on-this-day-posts-form', $hidden, 'OTD Widget should not render the opt-in form after the user opts out.' ); } /** @@ -324,17 +323,16 @@ public function test_otd_activity_is_displayed_after_opt_in() { update_user_option( self::$user_id, 'dashboard_show_on_this_day', 'opt-in' ); $this->create_matching_post( self::$user_id, 'A note from me' ); - $otd_activity = get_echo( 'wp_dashboard_on_this_day' ); - $inline_styles = $this->get_inline_styles_by_id( $otd_activity ); + $otd_activity = get_echo( 'wp_dashboard_on_this_day' ); + $hidden = $this->get_hidden_class_by_id( $otd_activity ); - $this->assertArrayNotHasKey( 'wp-on-this-day-posts-form', $inline_styles, 'OTD Widget should not render the opt-in form after the user opts in.' ); + $this->assertArrayHasKey( 'wp-on-this-day-posts', $hidden, 'OTD Widget should render the section container.' ); + $this->assertFalse( $hidden['wp-on-this-day-posts'], 'OTD Widget section container should not be hidden after the user opts in.' ); - $this->assertArrayHasKey( 'wp-on-this-day-posts-list', $inline_styles, 'OTD Widget should render the posts list element.' ); - $this->assertSame( - 'block', - $inline_styles['wp-on-this-day-posts-list']['display'] ?? 'block', - 'OTD Widget posts list should not set an inline display property other than block after the user opts in.' - ); + $this->assertArrayHasKey( 'wp-on-this-day-posts-list', $hidden, 'OTD Widget should render the posts list element.' ); + $this->assertFalse( $hidden['wp-on-this-day-posts-list'], 'OTD Widget posts list should not be hidden after the user opts in.' ); + + $this->assertArrayNotHasKey( 'wp-on-this-day-posts-form', $hidden, 'OTD Widget should not render the opt-in form after the user opts in.' ); } /** @@ -465,15 +463,15 @@ public function test_recent_comments_widget_remains_registered_after_comments_ar } /** - * Gets the inline style declarations of each element with an ID. + * Gets whether each element with an ID has the `hidden` class. * * @param string $html HTML fragment to parse. - * @return array> Declarations keyed by element ID, then by lowercase property name. + * @return array Whether the element has the `hidden` class, keyed by element ID. */ - private function get_inline_styles_by_id( string $html ): array { + private function get_hidden_class_by_id( string $html ): array { $processor = WP_HTML_Processor::create_fragment( $html ); $this->assertInstanceOf( WP_HTML_Processor::class, $processor ); - $inline_styles = array(); + $hidden = array(); while ( $processor->next_tag() ) { $id = $processor->get_attribute( 'id' ); @@ -481,21 +479,10 @@ private function get_inline_styles_by_id( string $html ): array { continue; } - $declarations = array(); - foreach ( explode( ';', (string) $processor->get_attribute( 'style' ) ) as $declaration ) { - if ( ! str_contains( $declaration, ':' ) ) { - continue; - } - - list( $property, $value ) = explode( ':', $declaration, 2 ); - - $declarations[ strtolower( trim( $property ) ) ] = trim( $value ); - } - - $inline_styles[ $id ] = $declarations; + $hidden[ $id ] = true === $processor->has_class( 'hidden' ); } - return $inline_styles; + return $hidden; } /**