Widgets: Improve caching within get_calendar().

Improves caching of the `get_calendar()` function by:
* fixing incorrect cache collisions for different `initial` `post_type` and week values, and,
* ensuring parameter equivalents generate the same cache key, ie passing the same values in a different order.

Improves tests for the function by:
* navigating to February 2025 in test set up to ensure the correct calendar month is displayed,
* adding messages for tests with multiple assertions,
* improving the tests for the calendar captions by wrapping the expected value in the HTML tag,
* adding dedicated test for the different `initial` parameter,
* ensuring caches do not collide for different parameters, and,
* ensuring caches do collide for equivalent parameters.

Follow up to r4522, r59908, r59909, r59917 (reverted), r59918 (reverted), r59930.

Props peterwilsoncc, jorbin, audrasjb.
Fixes #34093.


git-svn-id: https://develop.svn.wordpress.org/trunk@59939 602fd350-edb4-49c9-b593-d223f7449a82
This commit is contained in:
Peter Wilson 2025-03-05 22:36:38 +00:00
parent 69807a52ec
commit 9c2527af42
2 changed files with 153 additions and 22 deletions

View File

@ -2292,7 +2292,39 @@ function get_calendar( $args = array() ) {
*/
$args = apply_filters( 'get_calendar_args', wp_parse_args( $args, $defaults ) );
$key = md5( $m . $monthnum . $year );
if ( ! post_type_exists( $args['post_type'] ) ) {
$args['post_type'] = 'post';
}
$w = 0;
if ( isset( $_GET['w'] ) ) {
$w = (int) $_GET['w'];
}
/*
* Normalize the cache key.
*
* The following ensures the same cache key is used for the same parameter
* and parameter equivalents. This prevents `post_type > post, initial > true`
* from generating a different key from the same values in the reverse order.
*
* `display` is excluded from the cache key as the cache contains the same
* HTML regardless of this functions need to echo or return the output.
*
* The global values contain data generated by the URL querystring variables.
*/
$cache_args = $args;
unset( $cache_args['display'] );
$cache_args['globals'] = array(
'm' => $m,
'monthnum' => $monthnum,
'year' => $year,
'week' => $w,
);
wp_recursive_ksort( $cache_args );
$key = md5( serialize( $cache_args ) );
$cache = wp_cache_get( 'get_calendar', 'calendar' );
if ( $cache && is_array( $cache ) && isset( $cache[ $key ] ) ) {
@ -2312,9 +2344,6 @@ function get_calendar( $args = array() ) {
}
$post_type = $args['post_type'];
if ( ! post_type_exists( $post_type ) ) {
$post_type = 'post';
}
// Quick check. If we have no posts at all, abort!
if ( ! $posts ) {
@ -2327,9 +2356,6 @@ function get_calendar( $args = array() ) {
}
}
if ( isset( $_GET['w'] ) ) {
$w = (int) $_GET['w'];
}
// week_begins = 0 stands for Sunday.
$week_begins = (int) get_option( 'start_of_week' );

View File

@ -30,6 +30,29 @@ class Tests_General_GetCalendar extends WP_UnitTestCase {
'post_date' => '2025-02-01 12:00:00',
)
);
self::factory()->post->create(
array(
'post_type' => 'page',
'post_date' => '2025-02-03 12:00:00',
)
);
}
/**
* Set up for each test.
*/
public function set_up() {
parent::set_up();
/*
* Navigate to February 2025.
*
* All posts within this test suite are published in February 2025,
* navigating to the month ensures that the correct month is displayed
* in the calendar to allow the assertions to pass.
*/
$this->go_to( '/?m=202502' );
}
/**
@ -38,9 +61,11 @@ class Tests_General_GetCalendar extends WP_UnitTestCase {
* @ticket 34093
*/
public function test_get_calendar_display() {
$expected = '<table id="wp-calendar"';
$actual = get_echo( 'get_calendar', array( array( 'display' => true ) ) );
$this->assertStringContainsString( $expected, $actual );
$calendar_html = get_echo( 'get_calendar', array( array( 'display' => true ) ) );
$this->assertStringContainsString( '<th scope="col" aria-label="Monday">M</th>', $calendar_html, 'Calendar is expected to use initials for day names' );
$this->assertStringContainsString( '<table id="wp-calendar"', $calendar_html, 'Calendar is expected to contain the element table#wp-calendar' );
$this->assertStringContainsString( 'Posts published on February 1, 2025', $calendar_html, 'Calendar is expected to display posts published on February 1, 2025.' );
$this->assertStringContainsString( '<caption>February 2025</caption', $calendar_html, 'Calendar is expected to be captioned February 2025.' );
}
/**
@ -49,13 +74,6 @@ class Tests_General_GetCalendar extends WP_UnitTestCase {
* @ticket 34093
*/
public function test_get_calendar_args_filter() {
$page_id = self::factory()->post->create(
array(
'post_type' => 'page',
'post_date' => '2025-02-03 12:00:00',
)
);
add_filter(
'get_calendar_args',
function ( $args ) {
@ -66,9 +84,95 @@ class Tests_General_GetCalendar extends WP_UnitTestCase {
$calendar_html = get_echo( 'get_calendar' );
remove_all_filters( 'get_calendar_args' );
$this->assertStringContainsString( '<th scope="col" aria-label="Monday">M</th>', $calendar_html, 'Calendar is expected to use initials for day names' );
$this->assertStringContainsString( '<table id="wp-calendar"', $calendar_html, 'Calendar is expected to contain the element table#wp-calendar' );
$this->assertStringContainsString( 'Posts published on February 3, 2025', $calendar_html, 'Calendar is expected to display page published on February 3, 2025.' );
$this->assertStringNotContainsString( 'Posts published on February 1, 2025', $calendar_html, 'Calendar is not expected to display posts published on February 1, 2025.' );
$this->assertStringContainsString( '<caption>February 2025</caption', $calendar_html, 'Calendar is expected to be captioned February 2025.' );
}
$this->assertStringContainsString( '<table id="wp-calendar"', $calendar_html );
/**
* Test that get_calendar() respects the args post type parameter.
*
* @ticket 34093
*/
public function test_get_calendar_post_type_args() {
$calendar_html = get_echo( 'get_calendar', array( array( 'post_type' => 'page' ) ) );
$this->assertStringContainsString( '<th scope="col" aria-label="Monday">M</th>', $calendar_html, 'Calendar is expected to use initials for day names' );
$this->assertStringContainsString( '<table id="wp-calendar"', $calendar_html, 'Calendar is expected to contain the element table#wp-calendar' );
$this->assertStringContainsString( 'Posts published on February 3, 2025', $calendar_html, 'Calendar is expected to display page published on February 3, 2025.' );
$this->assertStringNotContainsString( 'Posts published on February 1, 2025', $calendar_html, 'Calendar is not expected to display posts published on February 1, 2025.' );
$this->assertStringContainsString( '<caption>February 2025</caption', $calendar_html, 'Calendar is expected to be captioned February 2025.' );
}
/**
* Test that get_calendar() respects the args initial parameter.
*
* @ticket 34093
*/
public function test_get_calendar_initial_args() {
$first_calendar_html = get_echo( 'get_calendar', array( array( 'initial' => true ) ) );
$second_calendar_html = get_echo( 'get_calendar', array( array( 'initial' => false ) ) );
$this->assertStringContainsString( '<th scope="col" aria-label="Monday">M</th>', $first_calendar_html, 'First calendar is expected to use initials for day names' );
$this->assertStringContainsString( '<th scope="col" aria-label="Monday">Mon</th>', $second_calendar_html, 'Second calendar is expected to use abbreviations for day names' );
}
/**
* Test that get_calendar() uses a different cache for different arguments.
*
* @ticket 34093
*/
public function test_get_calendar_caching_accounts_for_args() {
$first_calendar_html = get_echo( 'get_calendar' );
$second_calendar_html = get_echo( 'get_calendar', array( array( 'post_type' => 'page' ) ) );
$this->assertNotSame( $first_calendar_html, $second_calendar_html, 'Each calendar should be different' );
}
/**
* Test that get_calendar() uses the same cache for equivalent arguments.
*
* @ticket 34093
*/
public function test_get_calendar_caching_accounts_for_equivalent_args() {
get_echo( 'get_calendar', array( array( 'post_type' => 'page' ) ) );
$num_queries_start = get_num_queries();
// Including an argument that is the same as the default value shouldn't miss the cache.
get_echo(
'get_calendar',
array(
array(
'post_type' => 'page',
'initial' => true,
),
)
);
// Changing the order of arguments shouldn't miss the cache.
get_echo(
'get_calendar',
array(
array(
'initial' => true,
'post_type' => 'page',
),
)
);
// Display param should be ignored for the cache.
get_calendar(
array(
'post_type' => 'page',
'initial' => true,
'display' => false,
)
);
$num_queries_end = get_num_queries();
$this->assertSame( 0, $num_queries_end - $num_queries_start, 'Cache should be hit for subsequent equivalent calendar queries.' );
}
/**
@ -83,8 +187,9 @@ class Tests_General_GetCalendar extends WP_UnitTestCase {
$second_calendar_html = get_calendar( false, false );
$this->assertStringContainsString( '<th scope="col" aria-label="Monday">Mon</th>', $first_calendar_html );
$this->assertStringContainsString( '<table id="wp-calendar"', $second_calendar_html );
$this->assertStringContainsString( '<th scope="col" aria-label="Monday">Mon</th>', $second_calendar_html );
$this->assertStringContainsString( '<th scope="col" aria-label="Monday">Mon</th>', $first_calendar_html, 'Calendar is expected to use abbreviations for day names' );
$this->assertStringContainsString( '<caption>February 2025</caption>', $first_calendar_html, 'Calendar is expected to be captioned February 2025' );
$this->assertStringContainsString( '<table id="wp-calendar"', $first_calendar_html, 'Calendar is expected to contain the element table#wp-calendar' );
$this->assertSame( $first_calendar_html, $second_calendar_html, 'Both calendars should be identical' );
}
}