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.' ); + } }