问题描述
我正在使用 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="' . get_permalink($page->ID) . '">' . $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 辅助翻译的英文资料结果。如果您对结果不满意,可以加入我们改善翻译效果:薇晓朵技术论坛。