diff --git a/src/wp-admin/edit-tags.php b/src/wp-admin/edit-tags.php
index 6efdeb9b0527d..cb2d72c62a21a 100644
--- a/src/wp-admin/edit-tags.php
+++ b/src/wp-admin/edit-tags.php
@@ -627,12 +627,40 @@
' . apply_filters( 'the_category', get_cat_name( get_option( 'default_category' ) ), '', '' ) . ''
+ '' . apply_filters( 'the_category', get_cat_name( $default_category_id ), '', '' ) . ''
);
+
+ $default_edit_link = current_user_can( 'edit_term', $default_category_id ) ? get_edit_term_link( $default_category_id, 'category' ) : null;
+ $can_change_default = current_user_can( 'manage_options' );
+
+ if ( $default_edit_link && $can_change_default ) {
+ echo ' ';
+ printf(
+ /* translators: 1: URL to edit the default category, 2: URL to Writing Settings. */
+ __( 'It can be renamed or you can choose a different default category.' ),
+ esc_url( $default_edit_link ),
+ esc_url( admin_url( 'options-writing.php#default_category' ) )
+ );
+ } elseif ( $default_edit_link ) {
+ echo ' ';
+ printf(
+ /* translators: %s: URL to edit the default category. */
+ __( 'It can be renamed.' ),
+ esc_url( $default_edit_link )
+ );
+ } elseif ( $can_change_default ) {
+ echo ' ';
+ printf(
+ /* translators: %s: URL to Writing Settings. */
+ __( 'You can choose a different default category.' ),
+ esc_url( admin_url( 'options-writing.php#default_category' ) )
+ );
+ }
?>
diff --git a/src/wp-admin/includes/class-wp-terms-list-table.php b/src/wp-admin/includes/class-wp-terms-list-table.php
index 9481f0967a744..3d6c9c1c002c8 100644
--- a/src/wp-admin/includes/class-wp-terms-list-table.php
+++ b/src/wp-admin/includes/class-wp-terms-list-table.php
@@ -383,6 +383,13 @@ public function column_cb( $item ) {
public function column_name( $tag ) {
$taxonomy = $this->screen->taxonomy;
+ $default_term_label = '';
+ if ( (int) get_option( 'default_' . $taxonomy ) === $tag->term_id
+ || (int) get_option( 'default_term_' . $taxonomy ) === $tag->term_id
+ ) {
+ $default_term_label = ' —
' . __( 'Default' ) . '';
+ }
+
$pad = str_repeat( '— ', max( 0, $this->level ) );
/**
@@ -420,8 +427,9 @@ public function column_name( $tag ) {
}
$output = sprintf(
- '
%s',
- $name
+ '
%s%s',
+ $name,
+ $default_term_label
);
/** This filter is documented in wp-admin/includes/class-wp-terms-list-table.php */
@@ -530,6 +538,19 @@ protected function handle_row_actions( $item, $column_name, $primary ) {
);
}
+ if ( 'category' === $taxonomy
+ && (int) get_option( 'default_category' ) === $tag->term_id
+ && current_user_can( 'manage_options' )
+ ) {
+ $actions['change-default'] = sprintf(
+ '
%s',
+ esc_url( admin_url( 'options-writing.php#default_category' ) ),
+ /* translators: %s: Taxonomy term name. */
+ esc_attr( sprintf( __( 'Change the default category from “%s”' ), $tag->name ) ),
+ __( 'Change Default' )
+ );
+ }
+
/**
* Filters the action links displayed for each term in the Tags list table.
*
diff --git a/tests/phpunit/tests/admin/wpTermsListTable.php b/tests/phpunit/tests/admin/wpTermsListTable.php
index 9071dfd4055c5..15b0da42de1ce 100644
--- a/tests/phpunit/tests/admin/wpTermsListTable.php
+++ b/tests/phpunit/tests/admin/wpTermsListTable.php
@@ -38,6 +38,17 @@ public function set_up() {
$this->terms_list_table = new WP_Terms_List_Table();
}
+ /**
+ * Builds a terms list table bound to a specific taxonomy screen so that
+ * `$this->screen->taxonomy` resolves correctly inside the table methods.
+ *
+ * @param string $taxonomy Taxonomy slug.
+ * @return WP_Terms_List_Table
+ */
+ private function get_terms_list_table_for_taxonomy( $taxonomy ) {
+ return new WP_Terms_List_Table( array( 'screen' => 'edit-' . $taxonomy ) );
+ }
+
/**
* Call an inaccessible (private or protected) method.
*
@@ -87,4 +98,144 @@ public function test_handle_row_actions_as_admin() {
$this->assertStringContainsString( 'Delete', $actions, 'Delete action should be displayed to the admin.' );
$this->assertStringContainsString( admin_url( 'term.php' ), $actions, 'Edit term link should be displayed to the admin.' );
}
+
+ /**
+ * @covers WP_Terms_List_Table::handle_row_actions()
+ *
+ * @ticket 26268
+ */
+ public function test_change_default_action_shown_for_default_category_as_admin() {
+ wp_set_current_user( self::$admin_id );
+
+ $default_category = self::factory()->term->create_and_get( array( 'taxonomy' => self::CATEGORY_TAXONOMY ) );
+ $previous_default = get_option( 'default_category' );
+ update_option( 'default_category', $default_category->term_id );
+
+ $list_table = $this->get_terms_list_table_for_taxonomy( self::CATEGORY_TAXONOMY );
+ $actions = $this->call_inaccessible_method( $list_table, 'handle_row_actions', array( $default_category, 'title', 'title' ) );
+
+ update_option( 'default_category', $previous_default );
+
+ $this->assertStringContainsString( 'change-default', $actions, 'Change Default action should be displayed for the default category.' );
+ $this->assertStringContainsString( esc_url( admin_url( 'options-writing.php#default_category' ) ), $actions, 'Change Default link should target the Writing Settings field.' );
+ }
+
+ /**
+ * @covers WP_Terms_List_Table::handle_row_actions()
+ *
+ * @ticket 26268
+ */
+ public function test_change_default_action_hidden_for_default_category_as_author() {
+ wp_set_current_user( self::$author_id );
+
+ $default_category = self::factory()->term->create_and_get( array( 'taxonomy' => self::CATEGORY_TAXONOMY ) );
+ $previous_default = get_option( 'default_category' );
+ update_option( 'default_category', $default_category->term_id );
+
+ $list_table = $this->get_terms_list_table_for_taxonomy( self::CATEGORY_TAXONOMY );
+ $actions = $this->call_inaccessible_method( $list_table, 'handle_row_actions', array( $default_category, 'title', 'title' ) );
+
+ update_option( 'default_category', $previous_default );
+
+ $this->assertStringNotContainsString( 'change-default', $actions, 'Change Default action should be hidden for users without manage_options.' );
+ }
+
+ /**
+ * @covers WP_Terms_List_Table::handle_row_actions()
+ *
+ * @ticket 26268
+ */
+ public function test_change_default_action_hidden_for_non_default_category() {
+ wp_set_current_user( self::$admin_id );
+
+ $default_category = self::factory()->term->create_and_get( array( 'taxonomy' => self::CATEGORY_TAXONOMY ) );
+ $non_default_category = self::factory()->term->create_and_get( array( 'taxonomy' => self::CATEGORY_TAXONOMY ) );
+ $previous_default = get_option( 'default_category' );
+ update_option( 'default_category', $default_category->term_id );
+
+ $list_table = $this->get_terms_list_table_for_taxonomy( self::CATEGORY_TAXONOMY );
+ $actions = $this->call_inaccessible_method( $list_table, 'handle_row_actions', array( $non_default_category, 'title', 'title' ) );
+
+ update_option( 'default_category', $previous_default );
+
+ $this->assertStringNotContainsString( 'change-default', $actions, 'Change Default action should only appear on the default category row.' );
+ }
+
+ /**
+ * @covers WP_Terms_List_Table::handle_row_actions()
+ *
+ * @ticket 26268
+ */
+ public function test_change_default_action_hidden_for_non_category_taxonomy() {
+ wp_set_current_user( self::$admin_id );
+
+ $tag = self::factory()->term->create_and_get( array( 'taxonomy' => 'post_tag' ) );
+ $previous_default = get_option( 'default_category' );
+ // Match the term ID so only the taxonomy check can suppress the action.
+ update_option( 'default_category', $tag->term_id );
+
+ $list_table = $this->get_terms_list_table_for_taxonomy( 'post_tag' );
+ $actions = $this->call_inaccessible_method( $list_table, 'handle_row_actions', array( $tag, 'title', 'title' ) );
+
+ update_option( 'default_category', $previous_default );
+
+ $this->assertStringNotContainsString( 'change-default', $actions, 'Change Default action should not appear on non-category taxonomies.' );
+ }
+
+ /**
+ * @covers WP_Terms_List_Table::column_name()
+ *
+ * @ticket 26268
+ */
+ public function test_column_name_shows_default_label_for_default_category() {
+ wp_set_current_user( self::$admin_id );
+
+ $default_category = self::factory()->term->create_and_get( array( 'taxonomy' => self::CATEGORY_TAXONOMY ) );
+ $previous_default = get_option( 'default_category' );
+ update_option( 'default_category', $default_category->term_id );
+
+ $list_table = $this->get_terms_list_table_for_taxonomy( self::CATEGORY_TAXONOMY );
+ $output = $list_table->column_name( $default_category );
+
+ update_option( 'default_category', $previous_default );
+
+ $this->assertStringContainsString( 'taxonomy-default-label', $output, 'Default label markup should be rendered for the default category.' );
+ }
+
+ /**
+ * @covers WP_Terms_List_Table::column_name()
+ *
+ * @ticket 26268
+ */
+ public function test_column_name_shows_default_label_for_default_term_custom_taxonomy() {
+ wp_set_current_user( self::$admin_id );
+
+ register_taxonomy( 'wptest_default_label_tax', 'post' );
+ $default_term = self::factory()->term->create_and_get( array( 'taxonomy' => 'wptest_default_label_tax' ) );
+ update_option( 'default_term_wptest_default_label_tax', $default_term->term_id );
+
+ $list_table = $this->get_terms_list_table_for_taxonomy( 'wptest_default_label_tax' );
+ $output = $list_table->column_name( $default_term );
+
+ delete_option( 'default_term_wptest_default_label_tax' );
+ unregister_taxonomy( 'wptest_default_label_tax' );
+
+ $this->assertStringContainsString( 'taxonomy-default-label', $output, 'Default label should appear for terms identified by default_term_{taxonomy}.' );
+ }
+
+ /**
+ * @covers WP_Terms_List_Table::column_name()
+ *
+ * @ticket 26268
+ */
+ public function test_column_name_omits_default_label_for_non_default_term() {
+ wp_set_current_user( self::$admin_id );
+
+ $non_default_category = self::factory()->term->create_and_get( array( 'taxonomy' => self::CATEGORY_TAXONOMY ) );
+
+ $list_table = $this->get_terms_list_table_for_taxonomy( self::CATEGORY_TAXONOMY );
+ $output = $list_table->column_name( $non_default_category );
+
+ $this->assertStringNotContainsString( 'taxonomy-default-label', $output, 'Default label should not appear on non-default terms.' );
+ }
}