web-dev-qa-db-fra.com

Comment filtrer les utilisateurs sur la page des utilisateurs admin par méta-champs personnalisé?

Le problème

WP apparaît pour supprimer la valeur de ma variable de requête avant qu'elle ne soit utilisée pour filtrer la liste des utilisateurs.

Mon code

Cette fonction ajoute une colonne personnalisée à ma table Utilisateurs sur /wp-admin/users.php:

function add_course_section_to_user_meta( $columns ) {
    $columns['course_section'] = 'Section';
    return $columns;
}
add_filter( 'manage_users_columns', 'add_course_section_to_user_meta' );

Cette fonction indique à WP comment renseigner les valeurs dans la colonne:

function manage_users_course_section( $val, $col, $uid ) {
    if ( 'course_section' === $col )
        return get_the_author_meta( 'course_section', $uid );
}
add_filter( 'manage_users_custom_column', 'manage_users_course_section' );

Cela ajoute une liste déroulante et un bouton Filter au-dessus du tableau Utilisateurs:

function add_course_section_filter() {
    echo '<select name="course_section" style="float:none;">';
    echo '<option value="">Course Section...</option>';
    for ( $i = 1; $i <= 3; ++$i ) {
        if ( $i == $_GET[ 'course_section' ] ) {
            echo '<option value="'.$i.'" selected="selected">Section '.$i.'</option>';
        } else {
            echo '<option value="'.$i.'">Section '.$i.'</option>';
        }
    }
    echo '<input id="post-query-submit" type="submit" class="button" value="Filter" name="">';
}
add_action( 'restrict_manage_users', 'add_course_section_filter' );

Cette fonction modifie la requête de l'utilisateur pour ajouter mon meta_query:

function filter_users_by_course_section( $query ) {
    global $pagenow;

    if ( is_admin() && 
         'users.php' == $pagenow && 
         isset( $_GET[ 'course_section' ] ) && 
         !empty( $_GET[ 'course_section' ] ) 
       ) {
        $section = $_GET[ 'course_section' ];
        $meta_query = array(
            array(
                'key'   => 'course_section',
                'value' => $section
            )
        );
        $query->set( 'meta_key', 'course_section' );
        $query->set( 'meta_query', $meta_query );
    }
}
add_filter( 'pre_get_users', 'filter_users_by_course_section' );

Les autres informations

Cela crée correctement ma liste déroulante. Lorsque je sélectionne une section de cours et que je clique sur Filter, la page est actualisée et course_section apparaît dans l'URL, mais aucune valeur ne lui est associée. Si je vérifie les requêtes HTTP, cela indique qu'il est soumis avec la valeur de variable correcte, mais il existe alors un 302 Redirect qui semble supprimer la valeur que j'ai sélectionnée.

Si je soumets la variable course_section en la saisissant directement dans l'URL, le filtre fonctionne comme prévu.

Mon code est basé sur ce code de Dave Court .

J'ai aussi essayé de mettre ma requête var en liste blanche en utilisant ce code, mais sans succès:

function add_course_section_query_var( $qvars ) {
    $qvars[] = 'course_section';
    return $qvars;
}
add_filter( 'query_vars', 'add_course_section_query_var' );

J'utilise WP 4.4. Des idées pourquoi mon filtre ne fonctionne pas?

9
morphatic

MISE À JOUR 2018-06-28

Alors que le code ci-dessous fonctionne généralement bien, voici une réécriture du code pour WP> = 4.6.0 (en utilisant PHP 7):

function add_course_section_filter( $which ) {

    // create sprintf templates for <select> and <option>s
    $st = '<select name="course_section_%s" style="float:none;"><option value="">%s</option>%s</select>';
    $ot = '<option value="%s" %s>Section %s</option>';

    // determine which filter button was clicked, if any and set section
    $button = key( array_filter( $_GET, function($v) { return __( 'Filter' ) === $v; } ) );
    $section = $_GET[ 'course_section_' . $button ] ?? -1;

    // generate <option> and <select> code
    $options = implode( '', array_map( function($i) use ( $ot, $section ) {
        return sprintf( $ot, $i, selected( $i, $section, false ), $i );
    }, range( 1, 3 ) ));
    $select = sprintf( $st, $which, __( 'Course Section...' ), $options );

    // output <select> and submit button
    echo $select;
    submit_button(__( 'Filter' ), null, $which, false);
}
add_action('restrict_manage_users', 'add_course_section_filter');

function filter_users_by_course_section($query)
{
    global $pagenow;
    if (is_admin() && 'users.php' == $pagenow) {
        $button = key( array_filter( $_GET, function($v) { return __( 'Filter' ) === $v; } ) );
        if ($section = $_GET[ 'course_section_' . $button ]) {
            $meta_query = [['key' => 'courses','value' => $section, 'compare' => 'LIKE']];
            $query->set('meta_key', 'courses');
            $query->set('meta_query', $meta_query);
        }
    }
}
add_filter('pre_get_users', 'filter_users_by_course_section');

J'ai intégré plusieurs idées de @birgire et @cale_b qui proposent également des solutions intéressantes à lire ci-dessous. Plus précisément, je:

  1. Utilisé la variable $which qui a été ajoutée dans v4.6.0
  2. Utilisation des meilleures pratiques pour i18n en utilisant des chaînes traduisibles, par exemple. __( 'Filter' )
  3. Boucles échangées pour les (plus à la mode?) array_map(), array_filter() et range()
  4. Utilisé sprintf() pour générer les modèles de balisage
  5. Utilisé la notation de tableau de crochets au lieu de array()

Enfin, j'ai découvert un bug dans mes solutions précédentes. Ces solutions privilégient toujours le TOP <select> par rapport au BOTTOM <select>. Ainsi, si vous avez sélectionné une option de filtre dans la liste déroulante supérieure, puis que vous en avez ensuite sélectionnée une dans la liste déroulante inférieure, le filtre utilisera toujours uniquement la valeur affichée en haut (si elle n'est pas vide). Cette nouvelle version corrige ce bug.

MISE À JOUR 2018-02-14

Ce problème a été corrigé depuis WP 4.6.0 et les modifications sont documentées dans la documentation officielle . La solution ci-dessous fonctionne toujours, cependant.

Ce qui a causé le problème (WP <4.6.0)

Le problème était que l'action restrict_manage_users est appelée deux fois: une fois AU-DESSUS du tableau Utilisateurs et une fois au-dessous. Cela signifie que deux listes déroulantes select sont créées avec le même nom . Lorsque vous cliquez sur le bouton Filter, quelle que soit la valeur figurant dans le deuxième élément select (c'est-à-dire celui AU-DESSOUS de la table) remplace la valeur du premier élément, c'est-à-dire AU-DESSUS de la table.

Si vous souhaitez vous plonger dans la source WP, l'action restrict_manage_users est déclenchée à partir de WP_Users_List_Table::extra_tablenav($which) , qui est la fonction qui crée le menu déroulant natif pour modifier le rôle d'un utilisateur. Cette fonction utilise la variable $which qui lui indique si elle crée la variable select au-dessus ou au-dessous du formulaire et lui permet de donner aux deux menus déroulants des attributs name différents. Malheureusement, la variable $which n'est pas transmise à l'action restrict_manage_users, nous devons donc trouver un autre moyen de différencier nos propres éléments personnalisés.

Une des façons de le faire, comme @Linnea le suggère ci-dessus, serait d’ajouter du JavaScript pour capturer le clic Filter et synchroniser les valeurs des deux menus déroulants. J'ai choisi une solution uniquement PHP que je vais décrire maintenant.

Comment le réparer

Vous pouvez tirer parti de la possibilité de transformer les entrées HTML en tableaux de valeurs, puis filtrer le tableau pour supprimer toutes les valeurs non définies. Voici le code:

function add_course_section_filter() {
    if ( isset( $_GET[ 'course_section' ]) ) {
        $section = $_GET[ 'course_section' ];
        $section = !empty( $section[ 0 ] ) ? $section[ 0 ] : $section[ 1 ];
    } else {
        $section = -1;
    }
    echo ' <select name="course_section[]" style="float:none;"><option value="">Course Section...</option>';
    for ( $i = 1; $i <= 3; ++$i ) {
        $selected = $i == $section ? ' selected="selected"' : '';
        echo '<option value="' . $i . '"' . $selected . '>Section ' . $i . '</option>';
    }
    echo '<input type="submit" class="button" value="Filter">';
}
add_action( 'restrict_manage_users', 'add_course_section_filter' );

function filter_users_by_course_section( $query ) {
    global $pagenow;

    if ( is_admin() && 
         'users.php' == $pagenow && 
         isset( $_GET[ 'course_section' ] ) && 
         is_array( $_GET[ 'course_section' ] )
        ) {
        $section = $_GET[ 'course_section' ];
        $section = !empty( $section[ 0 ] ) ? $section[ 0 ] : $section[ 1 ];
        $meta_query = array(
            array(
                'key' => 'course_section',
                'value' => $section
            )
        );
        $query->set( 'meta_key', 'course_section' );
        $query->set( 'meta_query', $meta_query );
    }
}
add_filter( 'pre_get_users', 'filter_users_by_course_section' );

Bonus: PHP 7 Refactor

Puisque je suis enthousiasmé par PHP 7, si vous utilisez WP sur un serveur PHP 7, voici une version plus courte et plus sexy utilisant le Opérateur de fusion nul ?? :

function add_course_section_filter() {
    $section = $_GET[ 'course_section' ][ 0 ] ?? $_GET[ 'course_section' ][ 1 ] ?? -1;
    echo ' <select name="course_section[]" style="float:none;"><option value="">Course Section...</option>';
    for ( $i = 1; $i <= 3; ++$i ) {
        $selected = $i == $section ? ' selected="selected"' : '';
        echo '<option value="' . $i . '"' . $selected . '>Section ' . $i . '</option>';
    }
    echo '<input type="submit" class="button" value="Filter">';
}
add_action( 'restrict_manage_users', 'add_course_section_filter' );

function filter_users_by_course_section( $query ) {
    global $pagenow;

    if ( is_admin() && 'users.php' == $pagenow) {
        $section = $_GET[ 'course_section' ][ 0 ] ?? $_GET[ 'course_section' ][ 1 ] ?? null;
        if ( null !== $section ) {
            $meta_query = array(
                array(
                    'key' => 'course_section',
                    'value' => $section
                )
            );
            $query->set( 'meta_key', 'course_section' );
            $query->set( 'meta_query', $meta_query );
        }
    }
}
add_filter( 'pre_get_users', 'filter_users_by_course_section' );

Prendre plaisir!

6
morphatic

J'ai testé votre code dans Wordpress 4.4 et Wordpress 4.3.1. Avec la version 4.4, je rencontre exactement le même problème que vous. Cependant, votre code fonctionne correctement dans la version 4.3.1!

Je pense que c'est un bug de Wordpress. Je ne sais pas si ça a déjà été rapporté. Je pense que la raison derrière le bogue pourrait être que le bouton d'envoi envoie les vars de requête deux fois. Si vous examinez les vars de requête, vous verrez que course_section est répertorié deux fois, une fois avec la valeur correcte et une fois vide.

Edit: Ceci est la solution JavaScript

Ajoutez-le simplement au fichier functions.php de votre thème et remplacez NAME_OF_YOUR_INPUT_FIELD par le nom de votre champ de saisie! Puisque WordPress charge automatiquement jQuery du côté de l’administrateur, vous n’avez pas besoin de mettre en file d'attente les scripts. Cet extrait de code ajoute simplement un écouteur de modification aux entrées de la liste déroulante, puis met automatiquement à jour l'autre liste déroulante pour qu'elle corresponde à la même valeur. Plus d'explications ici.

add_action( 'in_admin_footer', function() {
?>
<script type="text/javascript">
    var el = jQuery("[name='NAME_OF_YOUR_INPUT_FIELD']");
    el.change(function() {
        el.val(jQuery(this).val());
    });
</script>
<?php
} );

J'espère que cela t'aides!

4
Linnea Huxford

Dans le noyau, les noms d’entrée bottom / sont marqués du numéro d’instance, par exemple. new_role (en haut) et new_role2 (en bas). Il existe deux approches pour une convention d'appellation similaire, à savoir course_section1 (haut) et course_section2 (bas):

Approche n ° 1

Puisque la variable $which ( top , bottom ) n'est pas transmise au hook restrict_manage_users, nous pourrions contourner ce problème en créant notre propre version de ce hook:

Créons l'action hook wpse_restrict_manage_users qui a accès à une variable $which:

add_action( 'restrict_manage_users', function() 
{
    static $instance = 0;   
    do_action( 'wpse_restrict_manage_users', 1 === ++$instance ? 'top' : 'bottom'  );

} );

Ensuite, nous pouvons l'accrocher avec:

add_action( 'wpse_restrict_manage_users', function( $which )
{
    $name = 'top' === $which ? 'course_section1' : 'course_section2';

    // your stuff here
} );

où nous avons maintenant $name comme course_section1 au top et course_section2 au bas .

Approche # 2

Connectons-nous à restrict_manage_users pour afficher des listes déroulantes, avec un nom différent pour chaque instance:

function add_course_section_filter() 
{
    static $instance= 0;    

    // Dropdown options         
    $options = '';
    foreach( range( 1, 3 ) as $rng )
    {
        $options = sprintf( 
            '<option value="%1$d" %2$s>Section %1$d</option>',
            $rng,
            selected( $rng, get_selected_course_section(), 0 )
        );
    }

    // Display dropdown with a different name for each instance
    printf( 
        '<select name="%s" style="float:none;"><option value="0">%s</option>%s</select>', 
        'course_section' . ++$instance,
        __( 'Course Section...' ),
        $options 
    );


    // Button
    printf (
        '<input id="post-query-submit" type="submit" class="button" value="%s" name="">',
        __( 'Filter' )
    );
}
add_action( 'restrict_manage_users', 'add_course_section_filter' );

où nous avons utilisé la fonction principale selected() et la fonction d'assistance:

/**
 * Get the selected course section 
 * @return int $course_section
 */
function get_selected_course_section()
{
    foreach( range( 1, 2) as $rng )
        $course_section = ! empty( $_GET[ 'course_section' . $rng ] )
            ? $_GET[ 'course_section' . $rng ]
            : -1; // default

    return (int) $course_section;
}

Ensuite, nous pourrions également l'utiliser lorsque nous rechercherons la section de cours sélectionnée dans le rappel d'action pre_get_users.

4
birgire

Ceci est une solution Javascript différente qui peut être utile pour certaines personnes. Dans mon cas, j'ai simplement supprimé la deuxième liste de sélection (en bas). Je trouve que je n'utilise jamais les entrées du bas de toute façon ...

add_action( 'in_admin_footer', function() {
    ?>
    <script type="text/javascript">
        jQuery(".tablenav.bottom select[name='course_section']").remove();
        jQuery(".tablenav.bottom input#post-query-submit").remove();
    </script>
    <?php
} );
1
locomo

Solution non-JavaScript

Donnez à la sélection un nom "array-style", comme suit:

echo '<select name="course_section[]" style="float:none;">';

Ensuite, les deux paramètres sont passés (en haut et en bas de la table), et maintenant dans un format de tableau connu.

Ensuite, la valeur peut être utilisée comme ceci dans la fonction pre_get_users:

function filter_users_by_course_section( $query ) {
    global $pagenow;

    // if not on users page in admin, get out
    if ( ! is_admin() || 'users.php' != $pagenow ) {
        return;
    } 

    // if no section selected, get out
    if ( empty( $_GET['course_section'] ) ) {
        return;
    }

    // course_section is known to be set now, so load it
    $section = $_GET['course_section'];

    // the value is an array, and one of the two select boxes was likely
    // not set to anything, so use array_filter to eliminate empty elements
    $section = array_filter( $section );

    // the value is still an array, so get the first value
    $section = reset( $section );

    // now the value is a single value, such as 1
    $meta_query = array(
        array(
            'key' => 'course_section',
            'value' => $section
        )
    );

    $query->set( 'meta_key', 'course_section' );
    $query->set( 'meta_query', $meta_query );
}
1
cale_b

une autre solution

vous pouvez mettre votre boîte de sélection de filtre dans un fichier séparé comme user_list_filter.php

et utilisez require_once 'user_list_filter.php' dans votre fonction de rappel d'action

user_list_filter.php fichier:

<select name="course_section" style="float:none;">
    <option value="">Course Section...</option>
    <?php for ( $i = 1; $i <= 3; ++$i ) {
        if ( $i == $_GET[ 'course_section' ] ) { ?>
        <option value="<?=$i?>" selected="selected">Section <?=$i?></option>
        <?php } else { ?>
        <option value="<?=$i?>">Section <?=$i?></option>
        <?php }
     }?>
</select>
<input id="post-query-submit" type="submit" class="button" value="Filter" name="">

et dans votre rappel d'action:

function add_course_section_filter() {
    require_once 'user_list_filter.php';
}
0
Alpha Elf