問題描述

我正在使用 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="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 輔助翻譯的英文資料結果。如果您對結果不滿意,可以加入我們改善翻譯效果:薇曉朵技術論壇。