web-dev-qa-db-fra.com

Comment masquer les enfants des brouillons à l'aide de wp_list_pages ()?

J'affiche un plan de site simple avec wp_list_pages ();

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

wp_list_pages( $args );

Le problème est que, par défaut, cela montre également les enfants publiés des brouillons, comme ceci:

Page 1 (publiée) -> affichée

--- Page 2 (brouillon) -> non affiché

------ Page 3 (publié) -> affiché

Ce que j'aimerais réaliser, c'est:

Page 1 (publiée) -> affichée

--- Page 2 (brouillon) -> non affiché

------ Page 3 (publié) -> pas affiché

Je suspecte un Walker de faire l'affaire, mais je ne pourrais jamais vraiment comprendre comment cela fonctionne.

Existe-t-il un moyen de masquer ces pages enfants sans avoir à les définir toutes en mode brouillon?

Modifier :

Pour clarifier, essayons quelques images. Donc, vous avez un arbre avec la hiérarchie complète de vos pages. Nous grimpons dans l'arbre. Dès que nous rencontrons un brouillon, nous le réduisons. Naturellement, toutes les autres branches qui lui sont rattachées sont également rejetées (qu’il s’agisse de brouillons ou non). J'espère que ça l'explique mieux.

Voici un exemple avec une hiérarchie un peu profonde:

Page 1 (publiée) -> affichée

--- Page 2 (projet) -> non affiché <- Couper ici et exclure tous les autres enfants

------ Page 3 (publié) -> non affiché

--------- Page 4 (publié) -> non affiché

------------ Page 5 (brouillon) -> non affiché

--------------- Page 6 (publié) -> non affiché

7
mike23

Excellentes réponses ci-dessus. J'ai relevé le défi en essayant de trouver un autre moyen de résoudre ce problème.

Le paramètre exclude:

On pourrait essayer:

'exclude' => wpse_exclude_drafts_branches()

où:

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

et le nombre de requêtes dépend de la profondeur de l’arbre.

Mise à jour:

Le paramètre exclude_tree:

Je viens de remarquer le paramètre exclude_tree mentionné sur la page Codex . Je me demande donc si cela fonctionnerait (non testé) pour exclure la totalité des branches brouillon nœuds:

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

et ensuite utiliser:

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

avec wp_list_pages().

4
birgire

Sincèrement, j'ai trouvé les marcheurs personnalisés ennuyeux: parfois, ce qui peut être fait avec un simple filtre nécessite le codage de toute une classe et, mais c'est probablement moi, je n'aime pas vraiment la logique derrière les marcheurs WordPress.

C’est la raison pour laquelle j’utilise souvent une astuce pour filtrer les éléments avant qu’ils ne soient marchent . C'est un cours de Walker très simple:

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

Il s'agit d'un lecteur de contenu réutilisable tout usage qui permet de filtrer les éléments avant qu'ils ne soient transmis àrealqui doivent être transmis au constructeur.

Dans votre cas, vous devriez faire quelque chose comme:

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

Vous pouvez maintenant coder un rappel de filtre afin de filtrer les pages à l'aide de 'filterable_walker_elements' hook déclenché par FilterableWalker classe:

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 );
4
gmazzap

Utiliser une Walker personnalisée n’est pas si difficile, c’est comme ça:

  • Créez un class ;

    Une classe est un ensemble de variables et de fonctions utilisant ces variables.

  • En en prolongeant un autre;

    La classe étendue ou dérivée a toutes les variables et fonctions de la classe de base [...] et ce que vous ajoutez dans la définition étendue.

  • Comme ça:

    class Extended_Class extends Base_Class {
       // code
    }
  • Ce qui vous donne la possibilité de changer/étendre les méthodes aka fonctions de la classe de base qui a été étendue. De plus, vous pouvez/pouvez étendre en ajoutant des méthodes ou des variables à la classe étendue.

  • Pour bien comprendre et exploiter les possibilités, il est nécessaire d’approfondir les aspects de OOP: Classes and Objects de PHP. Mais ce serait trop ici et de toute façon pas le bon endroit.

Revenons donc à WordPress et à wp_list_pages() . La classe que nous souhaitons utiliser avec wp_list_pages(), la classe Walker_Page - source -, a été dérivée en étendant la classe Walker - source .

En suivant le schéma expliqué ci-dessus, nous allons faire la même chose:

class Wpse159627_Walker_Page extends Walker_Page {
    // code
}

Maintenant, Walker_Page a deux variables - $tree_type et $db_fields - et quatre méthodes - start_lvl(), end_lvl(), start_el() et end_el(). Les variables ne nous concernent pas, en ce qui concerne les méthodes que nous avons au moins à examiner de plus près start_el() et end_el() .

La première chose à voir est que ces deux méthodes ont le paramètre $page:

@param object $ page Objet de données de page.

Ce qui contient toutes les données pertinentes dont nous avons besoin, comme le post_parent, et est en gros un objet WP_Post/$post/"$page" . Remis par la get_pages() return

Un tableau contenant toutes les pages correspondant à la demande, ou false en cas d'échec. Le tableau retourné est un tableau d'objets "page".

dans la fonction wp_list_pages().

Ce que nous devons vérifier, c’est le statut de la publication du parent de la page actuelle. Pour ce faire, la fonction get_post_status() est disponible. Comme déterminé, nous pouvons utiliser l’objet $ page disponible pour le faire.

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

Maintenant, nous pouvons utiliser ceci pour vérifier le statut du parent de la page de courants:

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

Permet de l'implémenter dans notre classe Walker étendue:

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

La nouvelle classe peut être utilisée avec wp_list_pages() comme ceci:

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



Modifier:

Ajouter ceci pour des raisons de complétude, donc pour que cela fonctionne pour les arbres, tous les descendants, pas seulement les enfants. C'est pas la manière optimale de le faire cependant, suffisamment d'autres suggestions ont été faites.

Comme les fonctions get_ancestors() et get_post_ancestors() de WordPress ne sont pas conçues pour obtenir des brouillons, j'ai construit une fonction pour obtenir chaque ancêtre:

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

De plus, il est nécessaire de connaître le statut de ces ancêtres. Ce qui peut être fait avec la fonction suivante:

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

Ceci peut être utilisé pour remplacer le conditionnel expliqué ci-dessus:

$ancestors = wpse159627_get_all_post_ancestors( $page->ID );
$ancestors_status = wpse159627_get_all_status( $ancestors );
if ( ! in_array( 'draft', $ancestors_status ) ) {
    // code
}
3
Nicolai

Cette réponse offre un autre moyen de le faire. Le code s’explique assez bien, j’ai nommé tout littéralement pour le rendre plus compréhensible. Ce que j'ai fait est de construire une fonction qui détermine les brouillons et leurs descendants, qui peuvent ensuite être utilisés avec le paramètre exclude de wp_list_pages().

Fonction d'assistance:

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

Utilisation:

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

Si vous utilisez une version PHP inférieure à la version 5.3, vous avez besoin d’une version sans fermetures. Dans mon livre, pour le dire clairement, c'est une erreur d'opérer sur un niveau inférieur à 5.4. Mais je connais très bien les exigences de WordPress, PHP 5.2.4, alors voilà:

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