diff --git a/src/wp-admin/includes/template.php b/src/wp-admin/includes/template.php
index 418bfd7def697..a67a1769eb163 100644
--- a/src/wp-admin/includes/template.php
+++ b/src/wp-admin/includes/template.php
@@ -904,6 +904,8 @@ function touch_time( $edit = 1, $for_post = 1, $tab_index = 0, $multi = 0 ) {
*
* @since 1.5.0
* @since 4.7.0 Added the `$post_type` parameter.
+ * @since 7.2.0 Templates are no longer re-sorted here; the order from
+ * WP_Theme::get_post_templates() (by translated name) is preserved.
*
* @param string $default_template Optional. The template file name. Default empty.
* @param string $post_type Optional. Post type to get templates for. Default 'page'.
@@ -911,8 +913,6 @@ function touch_time( $edit = 1, $for_post = 1, $tab_index = 0, $multi = 0 ) {
function page_template_dropdown( $default_template = '', $post_type = 'page' ) {
$templates = get_page_templates( null, $post_type );
- ksort( $templates );
-
foreach ( array_keys( $templates ) as $template ) {
$selected = selected( $default_template, $templates[ $template ], false );
echo "\n\t';
diff --git a/src/wp-includes/class-wp-theme.php b/src/wp-includes/class-wp-theme.php
index 6a18e7a534daa..ae64ac8d228ae 100644
--- a/src/wp-includes/class-wp-theme.php
+++ b/src/wp-includes/class-wp-theme.php
@@ -1320,6 +1320,7 @@ public function get_files( $type = null, $depth = 0, $search_parent = false ) {
*
* @since 4.7.0
* @since 5.8.0 Include block templates.
+ * @since 7.2.0 Templates are sorted by their translated name.
*
* @return array[] Array of page template arrays, keyed by post type and filename,
* with the value of the translated header name.
@@ -1392,8 +1393,15 @@ public function get_post_templates() {
$post_template = $this->translate_header( 'Template Name', $post_template );
}
}
+ unset( $post_type, $post_template );
}
+ // Sort each post type's templates by their translated name, keeping the file names as keys.
+ foreach ( $post_templates as &$post_type ) {
+ uasort( $post_type, 'strnatcasecmp' );
+ }
+ unset( $post_type );
+
return $post_templates;
}
diff --git a/tests/phpunit/data/themedir1/page-templates-sort/a-template.php b/tests/phpunit/data/themedir1/page-templates-sort/a-template.php
new file mode 100644
index 0000000000000..f2fefd9456e9e
--- /dev/null
+++ b/tests/phpunit/data/themedir1/page-templates-sort/a-template.php
@@ -0,0 +1,5 @@
+
diff --git a/tests/phpunit/data/themedir1/page-templates-sort/b-template.php b/tests/phpunit/data/themedir1/page-templates-sort/b-template.php
new file mode 100644
index 0000000000000..0adcd83fe489e
--- /dev/null
+++ b/tests/phpunit/data/themedir1/page-templates-sort/b-template.php
@@ -0,0 +1,5 @@
+
diff --git a/tests/phpunit/data/themedir1/page-templates-sort/c-template.php b/tests/phpunit/data/themedir1/page-templates-sort/c-template.php
new file mode 100644
index 0000000000000..8d58782577cb5
--- /dev/null
+++ b/tests/phpunit/data/themedir1/page-templates-sort/c-template.php
@@ -0,0 +1,5 @@
+
diff --git a/tests/phpunit/data/themedir1/page-templates-sort/index.php b/tests/phpunit/data/themedir1/page-templates-sort/index.php
new file mode 100644
index 0000000000000..db70785b6ee11
--- /dev/null
+++ b/tests/phpunit/data/themedir1/page-templates-sort/index.php
@@ -0,0 +1,3 @@
+
diff --git a/tests/phpunit/data/themedir1/page-templates-sort/section-10.php b/tests/phpunit/data/themedir1/page-templates-sort/section-10.php
new file mode 100644
index 0000000000000..f55509cc3d537
--- /dev/null
+++ b/tests/phpunit/data/themedir1/page-templates-sort/section-10.php
@@ -0,0 +1,5 @@
+
diff --git a/tests/phpunit/data/themedir1/page-templates-sort/section-2.php b/tests/phpunit/data/themedir1/page-templates-sort/section-2.php
new file mode 100644
index 0000000000000..3107edb2ff8c8
--- /dev/null
+++ b/tests/phpunit/data/themedir1/page-templates-sort/section-2.php
@@ -0,0 +1,5 @@
+
diff --git a/tests/phpunit/data/themedir1/page-templates-sort/style.css b/tests/phpunit/data/themedir1/page-templates-sort/style.css
new file mode 100644
index 0000000000000..72a34c67a5987
--- /dev/null
+++ b/tests/phpunit/data/themedir1/page-templates-sort/style.css
@@ -0,0 +1,11 @@
+/*
+Theme Name: Page Template Sorting Theme
+Theme URI: http://example.org/
+Description: An example theme for testing that page templates are sorted by name.
+Version: 0.1
+Author: Mr. WordPress
+Author URI: http://wordpress.org/
+
+This is just a stub to test that get_post_templates() sorts by the template name.
+
+*/
diff --git a/tests/phpunit/tests/admin/includesTemplate.php b/tests/phpunit/tests/admin/includesTemplate.php
index 4b9b8bc68034e..a402971e225ce 100644
--- a/tests/phpunit/tests/admin/includesTemplate.php
+++ b/tests/phpunit/tests/admin/includesTemplate.php
@@ -547,4 +547,32 @@ public function data_get_submit_button_shorthand() {
'raw button-compact unchanged' => array( 'button-compact', 'button button-compact' ),
);
}
+
+ /**
+ * The template drop-down should list options in the order provided by
+ * WP_Theme::get_post_templates() (by translated name), not re-sort them.
+ *
+ * @ticket 49194
+ *
+ * @covers ::page_template_dropdown
+ */
+ public function test_page_template_dropdown_preserves_name_order() {
+ $current_theme = wp_get_theme();
+ switch_theme( 'page-templates-sort' );
+
+ ob_start();
+ page_template_dropdown( '', 'page' );
+ $output = ob_get_clean();
+
+ switch_theme( $current_theme->get_stylesheet() );
+
+ // Options follow the translated name order from WP_Theme::get_post_templates().
+ $expected = "\n\t";
+ $expected .= "\n\t";
+ $expected .= "\n\t";
+ $expected .= "\n\t";
+ $expected .= "\n\t";
+
+ $this->assertSameIgnoreEOL( $expected, $output );
+ }
}
diff --git a/tests/phpunit/tests/theme/themeDir.php b/tests/phpunit/tests/theme/themeDir.php
index a953a04bc5533..f4f3dd10af490 100644
--- a/tests/phpunit/tests/theme/themeDir.php
+++ b/tests/phpunit/tests/theme/themeDir.php
@@ -169,6 +169,7 @@ public function test_theme_list() {
'My Subdir Theme', // Theme in a subdirectory should work.
'Page Template Child Theme', // Theme which inherits page templates.
'Page Template Theme', // Theme with page templates for other test code.
+ 'Page Template Sorting Theme', // Theme for testing page templates are sorted by name.
'Theme with Spaces in the Directory',
'Internationalized Theme',
'Custom Internationalized Theme',
diff --git a/tests/phpunit/tests/theme/wpThemeGetPostTemplates.php b/tests/phpunit/tests/theme/wpThemeGetPostTemplates.php
index 9a4775124cdbf..5fdb348ffc5fb 100644
--- a/tests/phpunit/tests/theme/wpThemeGetPostTemplates.php
+++ b/tests/phpunit/tests/theme/wpThemeGetPostTemplates.php
@@ -117,4 +117,29 @@ public function test_get_post_templates_uses_get_file_data() {
// Verify the `extra_theme_headers` filter is called.
$this->assertGreaterThan( 0, $filter->get_call_count(), 'The `extra_theme_headers` filter should be called at least once.' );
}
+
+ /**
+ * Templates should be ordered by their human-readable name, not by the file name.
+ *
+ * @ticket 49194
+ */
+ public function test_get_post_templates_are_sorted_by_name() {
+ $theme = wp_get_theme( 'page-templates-sort' );
+ $this->assertNotEmpty( $theme );
+
+ $post_templates = $theme->get_post_templates();
+ $this->assertArrayHasKey( 'page', $post_templates );
+
+ // Templates are sorted by name (case-insensitive, natural order), keeping the file names as keys.
+ $this->assertSame(
+ array(
+ 'c-template.php' => 'Apple Template',
+ 'b-template.php' => 'mango template',
+ 'section-2.php' => 'Section 2',
+ 'section-10.php' => 'Section 10',
+ 'a-template.php' => 'Zebra Template',
+ ),
+ $post_templates['page']
+ );
+ }
}