diff --git a/src/wp-admin/includes/class-wp-posts-list-table.php b/src/wp-admin/includes/class-wp-posts-list-table.php index c7d10fca217ef..0556aa24c7a21 100644 --- a/src/wp-admin/includes/class-wp-posts-list-table.php +++ b/src/wp-admin/includes/class-wp-posts-list-table.php @@ -1137,7 +1137,17 @@ public function column_title( $post ) { echo '
' . $locked_avatar . ' ' . $locked_text . "
\n"; } - $pad = str_repeat( '— ', $this->current_level ); + /** + * Filters the string used to indicate hierarchy level in the posts list table. + * + * The string is repeated once per level, so a child two levels deep will + * have the separator string prepended twice. + * + * @param string $separator The string used to indicate hierarchy level. Default '— '. + * @param WP_Post $post The current post object. + */ + $separator = apply_filters( 'post_title_child_separator', '— ', $post ); + $pad = str_repeat( $separator, $this->current_level ); echo ''; $title = _draft_or_post_title(); diff --git a/tests/phpunit/tests/admin/wpPostsListTable.php b/tests/phpunit/tests/admin/wpPostsListTable.php index 9d2482a034af7..34651201df7c4 100644 --- a/tests/phpunit/tests/admin/wpPostsListTable.php +++ b/tests/phpunit/tests/admin/wpPostsListTable.php @@ -309,6 +309,88 @@ public function test_empty_trash_button_should_not_be_shown_if_there_are_no_post $this->assertStringNotContainsString( 'id="delete_all"', $output ); } + /** + * Tests that the default em-dash separator is output for child pages. + * + * @ticket 39106 + * + * @covers WP_Posts_List_Table::column_title + */ + public function test_column_title_uses_default_separator_for_child_pages() { + // A child page has post_parent > 0, so column_title() will auto-calculate current_level. + $child = self::$children[1][1]; + + $this->table->set_hierarchical_display( true ); + + ob_start(); + $this->table->column_title( $child ); + $output = ob_get_clean(); + + $this->assertStringContainsString( '— ', $output ); + } + + /** + * Tests that the post_title_child_separator filter replaces the separator. + * + * @ticket 39106 + * + * @covers WP_Posts_List_Table::column_title + */ + public function test_post_title_child_separator_filter_changes_separator() { + $child = self::$children[1][1]; + + $this->table->set_hierarchical_display( true ); + + add_filter( + 'post_title_child_separator', + static function () { + return '> '; + } + ); + + ob_start(); + $this->table->column_title( $child ); + $output = ob_get_clean(); + + remove_all_filters( 'post_title_child_separator' ); + + $this->assertStringContainsString( '> ', $output ); + $this->assertStringNotContainsString( '— ', $output ); + } + + /** + * Tests that the post_title_child_separator filter receives the current WP_Post object. + * + * @ticket 39106 + * + * @covers WP_Posts_List_Table::column_title + */ + public function test_post_title_child_separator_filter_receives_post_object() { + $child = self::$children[1][1]; + + $this->table->set_hierarchical_display( true ); + + $received_post = null; + add_filter( + 'post_title_child_separator', + static function ( $separator, $post ) use ( &$received_post ) { + $received_post = $post; + return $separator; + }, + 10, + 2 + ); + + ob_start(); + $this->table->column_title( $child ); + ob_get_clean(); + + remove_all_filters( 'post_title_child_separator' ); + + $this->assertInstanceOf( WP_Post::class, $received_post ); + $this->assertSame( $child->ID, $received_post->ID ); + } + /** * @ticket 42066 *