diff --git a/src/wp-includes/general-template.php b/src/wp-includes/general-template.php
index 527289901e..3b0875f876 100644
--- a/src/wp-includes/general-template.php
+++ b/src/wp-includes/general-template.php
@@ -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' );
diff --git a/tests/phpunit/tests/general/getCalendar.php b/tests/phpunit/tests/general/getCalendar.php
index a2f4de4ee3..a8a3d1361f 100644
--- a/tests/phpunit/tests/general/getCalendar.php
+++ b/tests/phpunit/tests/general/getCalendar.php
@@ -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 = '
true ) ) );
- $this->assertStringContainsString( $expected, $actual );
+ $calendar_html = get_echo( 'get_calendar', array( array( 'display' => true ) ) );
+ $this->assertStringContainsString( 'M | ', $calendar_html, 'Calendar is expected to use initials for day names' );
+ $this->assertStringContainsString( 'assertStringContainsString( 'Posts published on February 1, 2025', $calendar_html, 'Calendar is expected to display posts published on February 1, 2025.' );
+ $this->assertStringContainsString( 'February 2025post->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( 'M | ', $calendar_html, 'Calendar is expected to use initials for day names' );
+ $this->assertStringContainsString( '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( 'February 2025assertStringContainsString( ' 'page' ) ) );
+
+ $this->assertStringContainsString( 'M | ', $calendar_html, 'Calendar is expected to use initials for day names' );
+ $this->assertStringContainsString( '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( 'February 2025 true ) ) );
+ $second_calendar_html = get_echo( 'get_calendar', array( array( 'initial' => false ) ) );
+
+ $this->assertStringContainsString( 'M | ', $first_calendar_html, 'First calendar is expected to use initials for day names' );
+ $this->assertStringContainsString( 'Mon | ', $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( 'Mon | ', $first_calendar_html );
- $this->assertStringContainsString( 'assertStringContainsString( 'Mon | ', $second_calendar_html );
+ $this->assertStringContainsString( 'Mon | ', $first_calendar_html, 'Calendar is expected to use abbreviations for day names' );
+ $this->assertStringContainsString( 'February 2025', $first_calendar_html, 'Calendar is expected to be captioned February 2025' );
+ $this->assertStringContainsString( 'assertSame( $first_calendar_html, $second_calendar_html, 'Both calendars should be identical' );
}
}