Wie kann ich Kinder von Entwurfsseiten mit wp_list_pages () ausblenden?

7

Ich zeige eine einfache Sitemap mit wp_list_pages () an.

$args = array(
    'sort_column' => 'menu_order',
    'title_li' => '',
    'post_status'  => 'publish'
);

wp_list_pages( $args );

Das Problem ist, dass standardmäßig auch die veröffentlichten untergeordneten Elemente von Entwurfsseiten angezeigt werden, z. B.:

Seite 1 (veröffentlicht) -> angezeigt

--- Seite 2 (Entwurf) -> nicht angezeigt

------ Seite 3 (veröffentlicht) -> angezeigt

Was ich erreichen möchte, ist:

Seite 1 (veröffentlicht) -> angezeigt

--- Seite 2 (Entwurf) -> nicht angezeigt

------ Seite 3 (veröffentlicht) -> nicht angezeigt

Ich vermute, ein benutzerdefinierter Walker würde den Trick machen, aber ich konnte nie wirklich verstehen, wie diese funktionieren.

Gibt es eine Möglichkeit, diese untergeordneten Seiten auszublenden, ohne sie alle auf Entwurf einstellen zu müssen?

Bearbeiten :

Versuchen wir zur Verdeutlichung einige Bilder. Sie haben also einen Baum mit der vollständigen Hierarchie Ihrer Seiten. Wir klettern auf den Baum. In dem Moment, in dem wir auf einen Entwurfszweig stoßen, schneiden wir ihn ab. Natürlich werden auch alle anderen weiter daran angebrachten Zweige verworfen (egal ob es sich um Entwürfe handelt oder nicht). Ich hoffe das erklärt es besser.

Hier ist ein Beispiel mit einer etwas tiefen Hierarchie:

Seite 1 (veröffentlicht) -> angezeigt

--- Seite 2 (Entwurf) -> nicht angezeigt <- Hier ausschneiden und alle weiteren Kinder ausschließen

------ Seite 3 (veröffentlicht) -> nicht angezeigt

--------- Seite 4 (veröffentlicht) -> nicht angezeigt

------------ Seite 5 (Entwurf) -> nicht angezeigt

--------------- Seite 6 (veröffentlicht) -> nicht angezeigt

mike23
quelle

Antworten:

4

Tolle Antworten oben. Ich nahm die Herausforderung an und versuchte, einen anderen Weg zu finden, um dies zu lösen.

Der excludeParameter:

Wir könnten es versuchen:

'exclude' => wpse_exclude_drafts_branches()

wo:

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 );
}

und die Anzahl der Abfragen hängt von der Baumtiefe ab.

Aktualisieren:

Der exclude_treeParameter:

Ich habe gerade den exclude_treeauf der Codex-Seite erwähnten Parameter bemerkt , daher frage ich mich, ob dies funktionieren würde (ungetestet), um den gesamten Zweig der Entwurfsknoten auszuschließen :

$exclude = get_posts(
    array( 
        'post_type'      => 'page',
        'fields'         => 'ids',
        'post_status'    => 'draft',
        'posts_per_page' => -1,
   )
);

und dann benutze:

'exclude_tree' => join( ',', $exclude ),

mit wp_list_pages().

Birgire
quelle
1
Ich frage mich, ob wir dies mit dem exclude_treeParameter noch viel vereinfachen können , um die gesamten Entwurfszweige auszuschließen.
Birgire
1
Ich kann bestätigen, dass exclude_treedie in Ihrem Update vorgeschlagene Option funktioniert. Was wirklich lustig ist, wenn man bedenkt, wie viel Code zur Beantwortung dieser Frage erstellt wurde. Ich denke, dies sollte die akzeptierte Antwort sein.
Nicolai
1
@ialocin Oh, es ist vielleicht nicht so elegant und tief wie die anderen Lösungen, die Sie und GM bereitgestellt haben, aber ich bin froh zu hören, dass es funktioniert ;-) Ich bin mir nicht sicher, warum ich diesen Parameter vorher auf der Codex-Seite nicht bemerkt habe , aber ich denke, das kann passieren, wenn die Nase stattdessen zu tief in den Quellcode
gesteckt wird ;-)
1
Ok, vielleicht ist es nicht so schick wie eine Lösung :) Aber es ist eingebaut und benötigt nur etwa 8 zusätzliche Zeilen Code, also ist das eigentlich gar nicht so schlecht. Ich denke du hast recht, das ist was passiert ..;)
Nicolai
2
Ich habe meinen vorherigen Kommentar entfernt, da dies jetzt funktioniert. Und +1. Was exclude_treetut, ist das, was @ialocin in seinem Code getan hat: get_page_childreninnerhalb einer Schleife aufzurufen . Nun diese Funktion [ruft sich rekursiv auf]. ( Developer.wordpress.org/reference/functions/wp_parse_id_list ). Innerhalb einer for-Schleife von möglicherweise Hunderten von Posts. Ergebnis: Für diese Aufgabe sind 8 Zeilen erforderlich, aber es ist möglich, dass ein Dutzend von Hundert-Funktionen aufgerufen werden. Ich habe keinen Zweifel, dass dies der richtige WordPress-Weg ist, aber wie jemand in The Loop sagte: "Niemand macht Dinge auf die
beste
4

Mit freundlichen Grüßen, ich fand benutzerdefinierte Walker ärgerlich: Manchmal erfordert das Codieren einer ganzen Klasse, was mit einem einfachen Filter gemacht werden kann, und wahrscheinlich bin ich es, ich mag Logik hinter WordPress-Walkern nicht wirklich.

Dies ist der Grund , warum ich oft einen Trick , um Filterelemente verwenden , bevor sie ging . Es ist eine wirklich einfache Walker-Klasse:

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() );
  }
}

Dies ist ein wiederverwendbarer Allzweck-Walker, mit dem Elemente gefiltert werden können, bevor sie an einen echten Walker übergeben werden, der im Konstruktor übergeben werden muss.

In Ihrem Fall sollten Sie Folgendes tun:

$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 );

Jetzt können Sie einen Filterrückruf codieren, um Seiten mit einem 'filterable_walker_elements'von der FilterableWalkerKlasse ausgelösten Hook zu filtern :

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 );
gmazzap
quelle
1
Vielen Dank für Ihre ausführliche Antwort! Obwohl es nicht das zu tun scheint, wonach ich suche. Ich zeige eine Sitemap von Seiten (wie einen Baum) an. Immer wenn wir zu einer Seite kommen, die sich im Entwurf befindet, möchte ich aufhören, eine andere Seite weiter unten in der Hierarchie dieses bestimmten Zweigs anzuzeigen. Ich werde versuchen, mit Ihrem Code herumzuspielen, um zu sehen, was ich finden kann.
Mike23
1
@ mike23 Ich habe die Antwort bearbeitet, jetzt sollte es funktionieren. Vor dem Bearbeiten wurden nur direkte untergeordnete Elemente einer Entwurfsseite entfernt. Jetzt sollte jede Seite in der Hierarchie einer Entwurfsseite entfernt werden.
gmazzap
@GM Funktioniert dies unabhängig von der Tiefe des ersten Seitenentwurfs?
Mike23
@ Mike23 ja, absolut
gmazzap
3

Die Verwendung eines Brauchs Walkerist eigentlich gar nicht so schwer, es geht im Grunde so:

  • Erstellen Sie eine Klasse .

    Eine Klasse ist eine Sammlung von Variablen und Funktionen, die mit diesen Variablen arbeiten.

  • Durch die Erweiterung eines anderen;

    Die erweiterte oder abgeleitete Klasse enthält alle Variablen und Funktionen der Basisklasse [...] und was Sie in der erweiterten Definition hinzufügen.

  • So was:

    class Extended_Class extends Base_Class {
       // code
    }
  • Dies gibt Ihnen die Möglichkeit, die Methoden oder Funktionen der erweiterten Basisklasse zu ändern / zu erweitern. Zusätzlich können / könnten Sie erweitern, indem Sie der erweiterten Klasse Methoden oder Variablen hinzufügen.

  • Um die Möglichkeiten vollständig zu verstehen und zu nutzen, ist es notwendig, tiefer in die OOP: Klassen- und Objektaspekte von PHP einzusteigen. Aber das wäre hier zu viel und sowieso nicht der richtige Ort.

Kommen wir also zurück zu WordPress und wp_list_pages(). Die Klasse, die wir erweitern möchten, um sie zu verwenden wp_list_pages(), die Walker_PageKlasse - Quelle - selbst, wurde durch Erweitern der Klasse Walker- Quelle abgeleitet .

Nach dem oben erläuterten Schema werden wir dasselbe tun:

class Wpse159627_Walker_Page extends Walker_Page {
    // code
}

Jetzt Walker_Pagehat zwei Variablen - $tree_typeund $db_fields- und vier Methoden - start_lvl(), end_lvl(), start_el()und end_el(). Die Variablen werden uns nicht betreffen, in Bezug auf die Methoden , die wir zumindest haben einen genaueren Blick auf , start_el()und end_el().

Das erste, was zu sehen ist, ist, dass diese beiden Methoden den Parameter haben $page:

@param Objekt $ Seite Seitendatenobjekt.

Welches alle relevanten Daten enthält, die wir brauchen, wie das post_parent, und so ziemlich ein WP_Post/ $post/ " $page" Objekt ist. Durch die get_pages()Rückgabe zurückgegeben

Ein Array, das alle Seiten enthält, die der Anforderung entsprechen, oder bei einem Fehler false. Das zurückgegebene Array ist ein Array von "Seiten" -Objekten.

innerhalb der wp_list_pages()Funktion.

Was wir überprüfen müssen, ist der Post-Status der aktuellen übergeordneten Seite. Dazu steht die Funktion get_post_status()zur Verfügung. Wie bestimmt können wir das dafür verfügbare $ page-Objekt verwenden.

$page_parent_id     = $page->post_parent;
$page_parent_status = get_post_status( $page_parent_id );

Jetzt können wir dies verwenden, um den Status der übergeordneten Seite der Stromseite zu überprüfen:

if ( $page_parent_status != 'draft' ) {
    // code
}

Implementieren wir es in unserer erweiterten Walker-Klasse:

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";
        }
    }
}

Die neue Klasse kann folgendermaßen verwendet werden wp_list_pages():

$args = array(
    'sort_column' => 'menu_order',
    'title_li'    => '',
    'post_status' => 'publish',
    'walker'      => new Wpse159627_Walker_Page
);
wp_list_pages( $args );



Bearbeiten:

Fügen Sie dies aus Gründen der Vollständigkeit hinzu, damit dies für Bäume funktioniert, für alle Nachkommen, nicht nur für Kinder. Es ist jedoch nicht der optimale Weg , es wurden genug andere Vorschläge gemacht.

Da WordPress get_ancestors()und get_post_ancestors()Funktionen nicht dazu gemacht sind, Entwürfe zu erhalten, habe ich eine Funktion erstellt, um jeden Vorfahren zu erhalten:

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;
}

Zusätzlich ist es notwendig, den Status dieser Vorfahren zu erhalten. Was mit der folgenden Funktion gemacht werden kann:

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;
}

Dies kann verwendet werden, um die oben erläuterte Bedingung zu ersetzen:

$ancestors = wpse159627_get_all_post_ancestors( $page->ID );
$ancestors_status = wpse159627_get_all_status( $ancestors );
if ( ! in_array( 'draft', $ancestors_status ) ) {
    // code
}
Nicolai
quelle
Das Problem bei diesem Ansatz ist, dass get_post_statusdas für jedes Element aufgerufene Element eine DB-Abfrage auslöst. Und eine DB-Abfrage für jede Seite ist keine gute Sache, wenn es viele Seiten gibt ...
gmazzap
Sicher, es ist nicht ganz optimal, aber glauben Sie mir, wenn ich so gut informiert wäre wie Sie, hätte ich eine Lösung vorgeschlagen, die so intelligent ist wie Ihre. @ GM
Nicolai
Ich hasse nur Wanderer und du auch nicht, also musste ich eine Lösung finden, um sie nicht zu benutzen :) Und glauben Sie mir, Sie sind
kompetent
Ok, wir sind uns einig, dass wir beide etwas fähig sind :) Mein Gefühl gegenüber Wanderern ist einfach gleichgültig :) Abgesehen davon ist das Lesen der Quelle plus Dokumentation und das Beantworten von Fragen nur eine weitere Möglichkeit für mich, ein bisschen mehr über die Angelegenheit zu lernen. @ GM
Nicolai
Das ist ein ganzes Tutorial, das du geschrieben hast! :) Ich werde sehen, ob ich es zum Laufen bringen kann. Im Moment löst Copypasta das Problem nicht. Ich sehe immer noch veröffentlichte Seiten, deren Eltern im Entwurf sind.
Mike23
2

Diese Antwort bietet eine andere Möglichkeit, dies zu tun. Der Code ist ziemlich selbsterklärend, ich habe alles ziemlich wörtlich genannt, um es besser verständlich zu machen. Was ich getan habe, ist eine Funktion zu konstruieren, die die Entwurfsseiten und ihre Nachkommen bestimmt, die dann mit dem excludeParameter von verwendet werden können wp_list_pages().

Hilfsfunktion:

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;
}

Verwendungszweck:

$args = array(
    'sort_column' => 'menu_order',
    'title_li'    => '',
    'post_status' => 'publish',
    'exclude'     => wpse159627_exclude_draft_sub_trees()
);
wp_list_pages( $args );

Wenn Sie eine PHP-Version kleiner als 5.3 verwenden, benötigen Sie eine Version ohne Schließungen. Um es klar zu sagen, in meinem Buch ist es ein Fehler, etwas unter 5.4 zu bearbeiten. Aber ich bin mir der WordPress-Anforderungen, PHP 5.2.4, sehr gut bewusst.

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;
}
Nicolai
quelle