Introduce a new algorithm for displaying a hierarchical list of post objects in the `WP_Posts_List_Table`. This reduces processing time, reduces database queries, and substantially reduces memory use on sites with a high number of Pages.
Props nofearinc, rodrigosprimo, nacin, johnbillion. Fixes #15459 git-svn-id: https://develop.svn.wordpress.org/trunk@31730 602fd350-edb4-49c9-b593-d223f7449a82
This commit is contained in:
parent
bea0628880
commit
82ac76c0a7
|
@ -86,6 +86,17 @@ class WP_Posts_List_Table extends WP_List_Table {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether the table layout should be hierarchical or not.
|
||||
*
|
||||
* @since 4.2.0
|
||||
*
|
||||
* @param bool $display Whether the table layout should be hierarchical.
|
||||
*/
|
||||
public function set_hierarchical_display( $display ) {
|
||||
$this->hierarchical_display = $display;
|
||||
}
|
||||
|
||||
public function ajax_user_can() {
|
||||
return current_user_can( get_post_type_object( $this->screen->post_type )->cap->edit_posts );
|
||||
}
|
||||
|
@ -95,7 +106,7 @@ class WP_Posts_List_Table extends WP_List_Table {
|
|||
|
||||
$avail_post_stati = wp_edit_posts_query();
|
||||
|
||||
$this->hierarchical_display = ( is_post_type_hierarchical( $this->screen->post_type ) && 'menu_order title' == $wp_query->query['orderby'] );
|
||||
$this->set_hierarchical_display( is_post_type_hierarchical( $this->screen->post_type ) && 'menu_order title' == $wp_query->query['orderby'] );
|
||||
|
||||
$total_items = $this->hierarchical_display ? $wp_query->post_count : $wp_query->found_posts;
|
||||
|
||||
|
@ -478,20 +489,20 @@ class WP_Posts_List_Table extends WP_List_Table {
|
|||
$count = 0;
|
||||
$start = ( $pagenum - 1 ) * $per_page;
|
||||
$end = $start + $per_page;
|
||||
$to_display = array();
|
||||
|
||||
foreach ( $pages as $page ) {
|
||||
if ( $count >= $end )
|
||||
break;
|
||||
|
||||
if ( $count >= $start ) {
|
||||
echo "\t";
|
||||
$this->single_row( $page, $level );
|
||||
$to_display[$page->ID] = $level;
|
||||
}
|
||||
|
||||
$count++;
|
||||
|
||||
if ( isset( $children_pages ) )
|
||||
$this->_page_rows( $children_pages, $count, $page->ID, $level + 1, $pagenum, $per_page );
|
||||
$this->_page_rows( $children_pages, $count, $page->ID, $level + 1, $pagenum, $per_page, $to_display );
|
||||
}
|
||||
|
||||
// If it is the last pagenum and there are orphaned pages, display them with paging as well.
|
||||
|
@ -502,14 +513,25 @@ class WP_Posts_List_Table extends WP_List_Table {
|
|||
break;
|
||||
|
||||
if ( $count >= $start ) {
|
||||
echo "\t";
|
||||
$this->single_row( $op, 0 );
|
||||
$to_display[$op->ID] = 0;
|
||||
}
|
||||
|
||||
$count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$ids = array_keys( $to_display );
|
||||
_prime_post_caches( $ids );
|
||||
|
||||
if ( ! isset( $GLOBALS['post'] ) ) {
|
||||
$GLOBALS['post'] = array_shift( $ids );
|
||||
}
|
||||
|
||||
foreach ( $to_display as $page_id => $level ) {
|
||||
echo "\t";
|
||||
$this->single_row( $page_id, $level );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -517,6 +539,7 @@ class WP_Posts_List_Table extends WP_List_Table {
|
|||
* together with paging support
|
||||
*
|
||||
* @since 3.1.0 (Standalone function exists since 2.6.0)
|
||||
* @since 4.2.0 Added the `$to_display` parameter.
|
||||
*
|
||||
* @param array $children_pages
|
||||
* @param int $count
|
||||
|
@ -524,8 +547,9 @@ class WP_Posts_List_Table extends WP_List_Table {
|
|||
* @param int $level
|
||||
* @param int $pagenum
|
||||
* @param int $per_page
|
||||
* @param array $to_display list of pages to be displayed
|
||||
*/
|
||||
private function _page_rows( &$children_pages, &$count, $parent, $level, $pagenum, $per_page ) {
|
||||
private function _page_rows( &$children_pages, &$count, $parent, $level, $pagenum, $per_page, &$to_display ) {
|
||||
|
||||
if ( ! isset( $children_pages[$parent] ) )
|
||||
return;
|
||||
|
@ -543,7 +567,13 @@ class WP_Posts_List_Table extends WP_List_Table {
|
|||
$my_parents = array();
|
||||
$my_parent = $page->post_parent;
|
||||
while ( $my_parent ) {
|
||||
$my_parent = get_post( $my_parent );
|
||||
// Get the ID from the list or the attribute if my_parent is an object
|
||||
$parent_id = $my_parent;
|
||||
if ( is_object( $my_parent ) ) {
|
||||
$parent_id = $my_parent->ID;
|
||||
}
|
||||
|
||||
$my_parent = get_post( $parent_id );
|
||||
$my_parents[] = $my_parent;
|
||||
if ( !$my_parent->post_parent )
|
||||
break;
|
||||
|
@ -551,20 +581,18 @@ class WP_Posts_List_Table extends WP_List_Table {
|
|||
}
|
||||
$num_parents = count( $my_parents );
|
||||
while ( $my_parent = array_pop( $my_parents ) ) {
|
||||
echo "\t";
|
||||
$this->single_row( $my_parent, $level - $num_parents );
|
||||
$to_display[$my_parent->ID] = $level - $num_parents;
|
||||
$num_parents--;
|
||||
}
|
||||
}
|
||||
|
||||
if ( $count >= $start ) {
|
||||
echo "\t";
|
||||
$this->single_row( $page, $level );
|
||||
$to_display[$page->ID] = $level;
|
||||
}
|
||||
|
||||
$count++;
|
||||
|
||||
$this->_page_rows( $children_pages, $count, $page->ID, $level + 1, $pagenum, $per_page );
|
||||
$this->_page_rows( $children_pages, $count, $page->ID, $level + 1, $pagenum, $per_page, $to_display );
|
||||
}
|
||||
|
||||
unset( $children_pages[$parent] ); //required in order to keep track of orphans
|
||||
|
@ -579,6 +607,9 @@ class WP_Posts_List_Table extends WP_List_Table {
|
|||
global $mode;
|
||||
|
||||
$global_post = get_post();
|
||||
|
||||
$post = get_post( $post );
|
||||
|
||||
$GLOBALS['post'] = $post;
|
||||
setup_postdata( $post );
|
||||
|
||||
|
|
|
@ -1039,6 +1039,7 @@ function wp_edit_posts_query( $q = false ) {
|
|||
$query['order'] = 'asc';
|
||||
$query['posts_per_page'] = -1;
|
||||
$query['posts_per_archive_page'] = -1;
|
||||
$query['fields'] = 'id=>parent';
|
||||
}
|
||||
|
||||
if ( ! empty( $q['show_sticky'] ) )
|
||||
|
|
|
@ -0,0 +1,179 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @group admin
|
||||
*/
|
||||
class Tests_Admin_includesListTable extends WP_UnitTestCase {
|
||||
function setUp() {
|
||||
set_current_screen( 'edit-page' );
|
||||
$GLOBALS['hook_suffix'] = '';
|
||||
$this->table = _get_list_table( 'WP_Posts_List_Table' );
|
||||
|
||||
parent::setUp();
|
||||
|
||||
// note that our top/children/grandchildren arrays are 1-indexed
|
||||
|
||||
// create top level pages
|
||||
$num_posts = 5;
|
||||
foreach ( range( 1, $num_posts ) as $i ) {
|
||||
$this->top[$i] = $this->factory->post->create_and_get( array(
|
||||
'post_type' => 'page',
|
||||
'post_title' => sprintf( 'Top Level Page %d', $i ),
|
||||
) );
|
||||
}
|
||||
|
||||
// create child pages
|
||||
$num_children = 3;
|
||||
foreach ( $this->top as $top => $top_page ) {
|
||||
foreach ( range( 1, $num_children ) as $i ) {
|
||||
$this->children[$top][$i] = $this->factory->post->create_and_get( array(
|
||||
'post_type' => 'page',
|
||||
'post_parent' => $top_page->ID,
|
||||
'post_title' => sprintf( 'Child %d', $i ),
|
||||
) );
|
||||
}
|
||||
}
|
||||
|
||||
// create grand-child pages for the third and fourth top-level pages
|
||||
$num_grandchildren = 3;
|
||||
foreach ( range( 3, 4 ) as $top ) {
|
||||
foreach ( $this->children[$top] as $child => $child_page ) {
|
||||
foreach ( range( 1, $num_grandchildren ) as $i ) {
|
||||
$this->grandchildren[$top][$child][$i] = $this->factory->post->create_and_get( array(
|
||||
'post_type' => 'page',
|
||||
'post_parent' => $child_page->ID,
|
||||
'post_title' => sprintf( 'Grandchild %d', $i ),
|
||||
) );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @ticket 15459
|
||||
*/
|
||||
function test_list_hierarchical_pages_first_page() {
|
||||
$this->_test_list_hierarchical_page( array(
|
||||
'paged' => 1,
|
||||
'posts_per_page' => 2,
|
||||
), array(
|
||||
$this->top[1]->ID,
|
||||
$this->children[1][1]->ID,
|
||||
) );
|
||||
}
|
||||
|
||||
/**
|
||||
* @ticket 15459
|
||||
*/
|
||||
function test_list_hierarchical_pages_second_page() {
|
||||
$this->_test_list_hierarchical_page( array(
|
||||
'paged' => 2,
|
||||
'posts_per_page' => 2,
|
||||
), array(
|
||||
$this->top[1]->ID,
|
||||
$this->children[1][2]->ID,
|
||||
$this->children[1][3]->ID,
|
||||
) );
|
||||
}
|
||||
|
||||
/**
|
||||
* @ticket 15459
|
||||
*/
|
||||
function test_search_hierarchical_pages_first_page() {
|
||||
$this->_test_list_hierarchical_page( array(
|
||||
'paged' => 1,
|
||||
'posts_per_page' => 2,
|
||||
's' => 'Child',
|
||||
), array(
|
||||
$this->children[1][1]->ID,
|
||||
$this->children[1][2]->ID,
|
||||
) );
|
||||
}
|
||||
|
||||
/**
|
||||
* @ticket 15459
|
||||
*/
|
||||
function test_search_hierarchical_pages_second_page() {
|
||||
$this->_test_list_hierarchical_page( array(
|
||||
'paged' => 2,
|
||||
'posts_per_page' => 2,
|
||||
's' => 'Top',
|
||||
), array(
|
||||
$this->top[3]->ID,
|
||||
$this->top[4]->ID,
|
||||
) );
|
||||
}
|
||||
|
||||
/**
|
||||
* @ticket 15459
|
||||
*/
|
||||
function test_grandchildren_hierarchical_pages_first_page() {
|
||||
// page 6 is the first page with grandchildren
|
||||
$this->_test_list_hierarchical_page( array(
|
||||
'paged' => 6,
|
||||
'posts_per_page' => 2,
|
||||
), array(
|
||||
$this->top[3]->ID,
|
||||
$this->children[3][1]->ID,
|
||||
$this->grandchildren[3][1][1]->ID,
|
||||
$this->grandchildren[3][1][2]->ID,
|
||||
) );
|
||||
}
|
||||
|
||||
/**
|
||||
* @ticket 15459
|
||||
*/
|
||||
function test_grandchildren_hierarchical_pages_second_page() {
|
||||
// page 7 is the second page with grandchildren
|
||||
$this->_test_list_hierarchical_page( array(
|
||||
'paged' => 7,
|
||||
'posts_per_page' => 2,
|
||||
), array(
|
||||
$this->top[3]->ID,
|
||||
$this->children[3][1]->ID,
|
||||
$this->grandchildren[3][1][3]->ID,
|
||||
$this->children[3][2]->ID,
|
||||
) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to test the output of a page which uses `WP_Posts_List_Table`.
|
||||
*
|
||||
* @param array $args Query args for the list of pages.
|
||||
* @param array $expected_ids Expected IDs of pages returned.
|
||||
*/
|
||||
protected function _test_list_hierarchical_page( array $args, array $expected_ids ) {
|
||||
$matches = array();
|
||||
|
||||
$_REQUEST['paged'] = $args['paged'];
|
||||
$GLOBALS['per_page'] = $args['posts_per_page'];
|
||||
|
||||
$args = array_merge( array(
|
||||
'post_type' => 'page',
|
||||
), $args );
|
||||
|
||||
// Mimic the behaviour of `wp_edit_posts_query()`:
|
||||
if ( ! isset( $args['orderby'] ) ) {
|
||||
$args['orderby'] = 'menu_order title';
|
||||
$args['order'] = 'asc';
|
||||
$args['posts_per_page'] = -1;
|
||||
$args['posts_per_archive_page'] = -1;
|
||||
}
|
||||
|
||||
$pages = new WP_Query( $args );
|
||||
|
||||
ob_start();
|
||||
$this->table->set_hierarchical_display( true );
|
||||
$this->table->display_rows( $pages->posts );
|
||||
$output = ob_get_clean();
|
||||
|
||||
preg_match_all( '|<tr[^>]*>|', $output, $matches );
|
||||
|
||||
$this->assertCount( count( $expected_ids ), array_keys( $matches[0] ) );
|
||||
|
||||
foreach ( $expected_ids as $id ) {
|
||||
$this->assertContains( sprintf( 'id="post-%d"', $id ), $output );
|
||||
}
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue