问题描述

我正在使用 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 – ,本身已经通过扩展类 Walkersource

按照上面的说明,我们将要做同样的事情:

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.

insidewp_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 辅助翻译的英文资料结果。如果您对结果不满意,可以加入我们改善翻译效果:薇晓朵技术论坛。