問題描述
我正在使用 wp_list_pages() 顯示一個簡單的站點 Map
$args = array(
'sort_column' => 'menu_order',
'title_li' => '',
'post_status' => 'publish'
);
wp_list_pages( $args );
問題是,默認情況下,這也顯示了已發佈的草稿頁面的孩子,如下所示:
Page 1 (published) -> displayed
— Page 2 (draft) -> not displayed
—— Page 3 (published) -> displayed
我想要實現的是:
Page 1 (published) -> displayed
— Page 2 (draft) -> not displayed
—— Page 3 (published) -> not displayed
我懷疑一個習慣的沃克會做的伎倆,但我永遠不會真正瞭解這些工作。
有沒有辦法隱藏這些子頁面,而不必將它們全部設置為草稿?
編輯:
為了澄清,讓我們嘗試一些圖像。所以你有一個樹與您的頁面的完整的層次結構。我們爬上樹。我們遇到一個分支機構的那一刻,我們把它砍下來。當然,所有附加的其他分支也會被丟棄 (不管是否是草稿) 。我希望能夠更好地解釋。
這裏有一個深層層次的例子:
Page 1 (published) -> displayed
— Page 2 (draft) -> not displayed <- Cut here and exclude all further children
—— Page 3 (published) -> not displayed
——— Page 4 (published) -> not displayed
———— Page 5 (draft) -> not displayed
————— Page 6 (published) -> not displayed
最佳解決方案
上面的很好的答案。我接受了挑戰,試圖找到另一種方法來解決這個問題。
exclude 參數:
我們可以試試:
'exclude' => wpse_exclude_drafts_branches()
哪裏:
function wpse_exclude_drafts_branches()
{
global $wpdb;
$exclude = array();
$results = $wpdb->get_col( "SELECT ID FROM {$wpdb->posts} where post_status = 'draft' AND post_type = 'page' " );
$exclude = array_merge( $exclude, $results) ;
while ( $results ):
$results = $wpdb->get_col( "SELECT DISTINCT ID FROM {$wpdb->posts} WHERE post_type = 'page' AND post_status = 'publish' AND post_parent > 0 AND post_parent IN (" . join( ',', $results ) . ") " );
$exclude = array_merge( $exclude, $results) ;
endwhile;
return join( ',', $exclude );
}
查詢次數取決於樹的深度。
更新:
exclude_tree 參數:
我剛剛注意到 Codex page 中提到的 exclude_tree 參數,所以我想知道這是否會工作 (未經測試) 排除整個草案節點分支:
$exclude = get_posts(
array(
'post_type' => 'page',
'fields' => 'ids',
'post_status' => 'draft',
'posts_per_page' => -1,
)
);
然後使用:
'exclude_tree' => join( ',', $exclude ),
與 wp_list_pages()。
次佳解決方案
Sincerly 我發現自定義的步行者很煩人:有時可以使用簡單的過濾器完成任務,需要整個類進行編碼,但可能是我,我不太喜歡 WordPress 步行者的邏輯。
這就是為什麼我經常使用一個把戲來過濾元素的原因。這是一個非常簡單的 Walker 類:
class FilterableWalker extends Walker {
private $walker;
function __construct( Walker $walker ) {
$this->walker = $walker;
}
function walk( array $elements = null, $max_depth = null ) {
$args = func_get_arg( 2 );
$filtered = apply_filters( 'filterable_walker_elements', $elements, $args, $this );
if ( is_array( $filtered ) ) {
$walk_args = func_get_args();
$walk_args[0] = $filtered ;
return call_user_func_array( array( $this->walker, 'walk' ), $walk_args );
}
return call_user_func_array( array( $this->walker, 'walk' ), func_get_args() );
}
function getWalker() {
return $this->walker;
}
function getWalkerClass() {
return get_class( $this->getWalker() );
}
}
這是一個 all-purpose,可重複使用的步行者,可以將其過濾到傳遞給構造函數的真實步行者之前進行過濾。
在你的情況下,你應該這樣做:
$args = array(
'sort_column' => 'menu_order',
'title_li' => '',
'post_status' => 'publish',
'skip_draft_children' => 1, // <- custom argument we will use in filter callback
'walker' => new FilterableWalker( new Walker_Page ) // <-- our walker
);
$pages = wp_list_pages( $args );
現在您可以使用 FilterableWalker 類引發的'filterable_walker_elements'鈎子對過濾器回調進行過濾:
add_filter( 'filterable_walker_elements', function( $elements, $args, $filterable ) {
$walker = $filterable->getWalkerClass();
if (
$walker === 'Walker_Page'
&& isset( $args['skip_draft_children'] )
&& $args['skip_draft_children'] // <-- our custom argument
) {
$ids = array_filter( array_unique( wp_list_pluck( $elements, 'post_parent' ) ) );
$parents = get_posts(
array(
'post__in' => $ids, 'post_status' => 'publish',
'fields' => 'ids', 'post_type' => 'page',
'nopaging' => true
)
);
$pages = $elements;
foreach( $pages as $i => $page ) {
if ( $page->post_parent !== 0 && ! in_array( $page->post_parent, $parents, true ) ) {
unset($elements[$i]);
$self_i = array_search( $page->ID, $parents, true );
if ( $self_i !== FALSE ) unset( $parents[$self_i] );
}
}
}
return $elements;
}, 10, 3 );
第三種解決方案
使用自定義的 Walker 實際上並不是那麼難,它基本上是這樣的:
-
創建一個 class; 類是使用這些變量的變量和函數的集合。
-
由 extending 另一個; 擴展或派生類具有基類 […] 的所有變量和函數以及在擴展定義中添加的內容。
-
喜歡這個:
class Extended_Class extends Base_Class { // code } -
這使您有可能更改/擴展已擴展的基類的 methods aka functions 。此外,您可以/可以通過向擴展類添加方法或變量來擴展。
-
要充分了解並利用可能性,需要深入瞭解 OOP: Classes and Objects 方面的 PHP 。但是這樣做太多了,而不是右邊的地方。
所以讓我們回到 WordPress 和 wp_list_pages()。我們想擴展的類使用 wp_list_pages(),Walker_Page 類 – source – ,本身已經通過擴展類 Walker – source 。
按照上面的説明,我們將要做同樣的事情:
class Wpse159627_Walker_Page extends Walker_Page {
// code
}
現在 Walker_Page 有兩個變量–$tree_type 和 $db_fields – 四種方法 – start_lvl(),end_lvl(),start_el()和 end_el()。這些變量不關心我們,關於我們至少必須仔細觀察 start_el()和 end_el()的方法。
首先要看的是這兩種方法有參數 $page:
@param object $page Page data object.
其中包含我們需要的所有相關數據,如 post_parent,並且幾乎是 WP_Post /$post /「$page」 對象。由 get_pages()返回
An array containing all the Pages matching the request, or false on failure. The returned array is an array of “page” objects.
inside 的 wp_list_pages()功能。
我們需要檢查的是當前頁面父的狀態,為此可以使用 get_post_status()功能。像決定一樣,我們可以使用 $ page 對象可以這樣做。
$page_parent_id = $page->post_parent;
$page_parent_status = get_post_status( $page_parent_id );
現在我們可以使用它來檢查當前頁面 parent 的狀態:
if ( $page_parent_status != 'draft' ) {
// code
}
讓我們在擴展的 Walker 類中實現它:
class Wpse159627_Walker_Page extends Walker_Page {
function start_el( &$output, $page, $depth = 0, $args = array(), $current_page = 0 ) {
$page_parent_id = $page->post_parent;
$page_parent_status = get_post_status( $page_parent_id );
if ( $page_parent_status != 'draft' ) {
if ( $depth )
$indent = str_repeat("t", $depth);
else
$indent = '';
extract($args, EXTR_SKIP);
$css_class = array('page_item', 'page-item-'.$page->ID);
if( isset( $args['pages_with_children'][ $page->ID ] ) )
$css_class[] = 'page_item_has_children';
if ( !empty($current_page) ) {
$_current_page = get_post( $current_page );
if ( in_array( $page->ID, $_current_page->ancestors ) )
$css_class[] = 'current_page_ancestor';
if ( $page->ID == $current_page )
$css_class[] = 'current_page_item';
elseif ( $_current_page && $page->ID == $_current_page->post_parent )
$css_class[] = 'current_page_parent';
} elseif ( $page->ID == get_option('page_for_posts') ) {
$css_class[] = 'current_page_parent';
}
$css_class = implode( ' ', apply_filters( 'page_css_class', $css_class, $page, $depth, $args, $current_page ) );
if ( '' === $page->post_title )
$page->post_title = sprintf( __( '#%d (no title)' ), $page->ID );
$output .= $indent . '<li class="' . $css_class . '"><a href="http://'%20.%20get_permalink($page->ID)%20.%20'">' . $link_before . apply_filters( 'the_title', $page->post_title, $page->ID ) . $link_after . '</a>';
if ( !empty($show_date) ) {
if ( 'modified' == $show_date )
$time = $page->post_modified;
else
$time = $page->post_date;
$output .= " " . mysql2date($date_format, $time);
}
}
}
function end_el( &$output, $page, $depth = 0, $args = array() ) {
$page_parent_id = $page->post_parent;
$page_parent_status = get_post_status( $page_parent_id );
if ( $page_parent_status != 'draft' ) {
$output .= "</li>n";
}
}
}
新課程可以與 wp_list_pages()一起使用,如下所示:
$args = array(
'sort_column' => 'menu_order',
'title_li' => '',
'post_status' => 'publish',
'walker' => new Wpse159627_Walker_Page
);
wp_list_pages( $args );
編輯:
加上這個是為了完整的原因,所以要使這個工作對於樹,所有後代,而不僅僅是孩子。這不是最好的方法,但是還有其他建議。
因為 WordPress 的 get_ancestors()和 get_post_ancestors()功能也沒有得到草稿,我構建了一個函數來獲得每個祖先:
function wpse159627_get_all_post_ancestors( $post_id ) {
$post_type = get_post_type( $post_id );
$post = new WP_Query(
array(
'page_id' => $post_id,
'include' => $post_id,
'post_type' => $post_type,
'post_status' => 'any',
'cache_results' => false,
'update_post_meta_cache' => false,
'update_post_term_cache' => false
)
);
$post = $post->posts[0];
if (
! $post
|| empty( $post->post_parent )
|| $post->post_parent == $post->ID
) {
return array();
}
$ancestors = array();
$id = $ancestors[] = $post->post_parent;
while (
$ancestor = new WP_Query(
array(
'page_id' => $id,
'include' => $id,
'post_type' => $post_type,
'post_status' => 'any',
'cache_results' => false,
'update_post_meta_cache' => false,
'update_post_term_cache' => false
)
)
) {
$ancestor = $ancestor->posts[0];
if (
empty( $ancestor->post_parent )
|| ( $ancestor->post_parent == $post->ID )
|| in_array( $ancestor->post_parent, $ancestors )
) {
break;
}
$id = $ancestors[] = $ancestor->post_parent;
}
return $ancestors;
}
此外,有必要獲得這些祖先的狀態。可以使用以下功能完成以下功能:
function wpse159627_get_all_status( $ids ) {
$status_arr = array();
foreach ( $ids as $id ) {
$post_type = get_post_type( $id );
$post = new WP_Query(
array(
'page_id' => $id,
'include' => $id,
'post_type' => $post_type,
'post_status' => 'any',
'cache_results' => false,
'update_post_meta_cache' => false,
'update_post_term_cache' => false
)
);
$post = $post->posts[0];
$status_arr[] = $post->post_status;
}
return $status_arr;
}
這可以用來替換上面説明的條件:
$ancestors = wpse159627_get_all_post_ancestors( $page->ID );
$ancestors_status = wpse159627_get_all_status( $ancestors );
if ( ! in_array( 'draft', $ancestors_status ) ) {
// code
}
第四種方案
這個答案提供了另一種做法。代碼幾乎是 self-explaining,我命名一切都很字面,使其更容易理解。我所做的是構建一個確定草稿頁及其後代的函數,可以與 wp_list_pages()的 exclude 參數一起使用。
助手功能:
function wpse159627_exclude_draft_sub_trees() {
$pages_any_status = get_posts(
array(
'post_type' => 'page',
'post_status' => 'any',
'posts_per_page' => -1,
// make this as inexpensive as possible
'cache_results' => false,
'update_post_meta_cache' => false,
'update_post_term_cache' => false
)
);
$draft_posts_ids = array_filter(
array_map(
function ( $array_to_map ) {
if( $array_to_map->post_status == 'draft' ) {
return $array_to_map->ID;
} else {
return null;
}
},
$pages_any_status
)
);
$children_of_draft_posts_arr_of_obj = array();
foreach ( $draft_posts_ids as $draft_id ) {
$children_of_draft_posts_arr_of_obj[] = get_page_children(
$draft_id,
$pages_any_status
);
}
$children_of_draft_posts = array();
foreach ( $children_of_draft_posts_arr_of_obj as $object ) {
foreach ( $object as $key => $value ) {
$children_of_draft_posts[] = $value;
}
}
$children_of_draft_posts_ids = array_map(
function ( $array_to_map ) {
return $array_to_map->ID;
},
$children_of_draft_posts
);
$exclude_from_list_pages = array_merge(
$draft_posts_ids,
$children_of_draft_posts_ids
);
$exclude_comma_sep_list = implode(',',$exclude_from_list_pages);
return $exclude_comma_sep_list;
}
用法:
$args = array(
'sort_column' => 'menu_order',
'title_li' => '',
'post_status' => 'publish',
'exclude' => wpse159627_exclude_draft_sub_trees()
);
wp_list_pages( $args );
如果您的 PHP 版本小於 5.3,則需要一個沒有關閉的版本。在我的書中,説得清楚,對 5.4 以下的任何一個操作都是錯誤的。但是我非常清楚 WordPress 的要求,PHP 5.2.4,所以這裏你去:
function wpse159627_extract_ids( $array_to_map ) {
return $array_to_map->ID;
}
function wpse159627_extract_ids_of_drafts( $array_to_map ) {
if( $array_to_map->post_status == 'draft' ) {
return $array_to_map->ID;
} else {
return null;
}
}
function wpse159627_exclude_draft_sub_trees_old_php() {
$pages_any_status = get_posts(
array(
'post_type' => 'page',
'post_status' => 'any',
'posts_per_page' => -1,
// make this as inexpensive as possible
'cache_results' => false,
'update_post_meta_cache' => false,
'update_post_term_cache' => false
)
);
$draft_posts_ids = array_filter(
array_map(
'wpse159627_extract_ids_of_drafts',
$pages_any_status
)
);
$children_of_draft_posts_arr_of_obj = array();
foreach ( $draft_posts_ids as $draft_id ) {
$children_of_draft_posts_arr_of_obj[] = get_page_children(
$draft_id,
$pages_any_status
);
}
$children_of_draft_posts = array();
foreach ( $children_of_draft_posts_arr_of_obj as $object ) {
foreach ( $object as $key => $value ) {
$children_of_draft_posts[] = $value;
}
}
$exclude_from_list_pages = array_merge(
$draft_posts_ids,
$children_of_draft_posts_ids
);
$exclude_comma_sep_list = implode(',',$exclude_from_list_pages);
return $exclude_comma_sep_list;
}
參考文獻
注:本文內容整合自 Google/Baidu/Bing 輔助翻譯的英文資料結果。如果您對結果不滿意,可以加入我們改善翻譯效果:薇曉朵技術論壇。