Wie filtere ich Benutzer auf der Seite der Administratorbenutzer nach benutzerdefinierten Metafeldern?

9

Das Problem

WP scheint den Wert meiner Abfragevariablen zu entfernen, bevor er zum Filtern der Benutzerliste verwendet wird.

Mein Code

Diese Funktion fügt meiner Benutzertabelle eine benutzerdefinierte Spalte hinzu /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' );

Diese Funktion teilt WP mit, wie Werte in die Spalte eingefügt werden sollen:

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

Dies fügt ein Dropdown und eine FilterSchaltfläche über der Benutzertabelle hinzu:

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

Diese Funktion ändert die Benutzerabfrage, um my hinzuzufügen 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' );

Andere Informationen

Es erstellt mein Dropdown korrekt. Wenn ich einen Kursabschnitt auswähle und auf Filterdie Seite klicke course_section, wird sie aktualisiert und in der URL angezeigt, ihr ist jedoch kein Wert zugeordnet. Wenn ich die HTTP-Anforderungen überprüfe, wird angezeigt, dass sie mit dem richtigen Variablenwert gesendet werden, aber es gibt eine 302 Redirect, die den von mir ausgewählten Wert zu entfernen scheint.

Wenn ich die course_sectionVariable durch direkte Eingabe in die URL sende, funktioniert der Filter wie erwartet.

Mein Code basiert ungefähr auf diesem Code von Dave Court .

Ich habe auch versucht, meine Abfragevariable mit diesem Code auf die Whitelist zu setzen, aber ohne Glück:

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

Ich benutze WP 4.4. Irgendwelche Ideen, warum mein Filter nicht funktioniert?

morphatisch
quelle
Zu Ihrer Information, ich habe auf der WP Trac-Website ein Ticket hinzugefügt , das verhindert, dass Entwickler durch einen der unten beschriebenen Rahmen springen müssen.
Morphatic

Antworten:

6

UPDATE 2018-06-28

Während der folgende Code meistens gut funktioniert, ist hier eine Neufassung des Codes für WP> = 4.6.0 (unter Verwendung von 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');

Ich habe einige Ideen von @birgire und @cale_b aufgenommen, die auch unten lesenswerte Lösungen anbieten. Insbesondere ich:

  1. Verwendete die $whichVariable, die hinzugefügt wurdev4.6.0
  2. Verwendete Best Practice für i18n unter Verwendung übersetzbarer Zeichenfolgen, z __( 'Filter' )
  3. Ausgetauschte Schlaufen für die (mehr in Mode?) array_map(), array_filter()Undrange()
  4. Wird sprintf()zum Generieren der Markup-Vorlagen verwendet
  5. Verwendet die eckige Klammer-Array-Notation anstelle von array()

Zuletzt habe ich einen Fehler in meinen früheren Lösungen entdeckt. Diese Lösungen bevorzugen immer das TOP <select>gegenüber dem BOTTOM <select>. Wenn Sie also eine Filteroption aus der oberen Dropdown-Liste ausgewählt und anschließend eine aus der unteren Dropdown-Liste ausgewählt haben, verwendet der Filter immer noch nur den Wert, der oben war (wenn er nicht leer ist). Diese neue Version behebt diesen Fehler.

UPDATE 14.02.2018

Dieses Problem wurde seit WP 4.6.0 behoben und die Änderungen sind in den offiziellen Dokumenten dokumentiert . Die folgende Lösung funktioniert jedoch immer noch.

Was hat das Problem verursacht (WP <4.6.0)

Das Problem war, dass die restrict_manage_usersAktion zweimal aufgerufen wird: einmal ÜBER der Benutzertabelle und einmal UNTEN. Dies bedeutet, dass ZWEI selectDropdowns mit demselben Namen erstellt werden . Wenn Sie auf die FilterSchaltfläche klicken, selectüberschreibt der Wert im zweiten Element (dh der Wert UNTER der Tabelle) den Wert im ersten Element, dh der Wert ÜBER der Tabelle.

Wenn Sie in die WP-Quelle eintauchen möchten, wird die restrict_manage_usersAktion von innen ausgelöst. WP_Users_List_Table::extra_tablenav($which)Dies ist die Funktion, mit der das native Dropdown-Menü erstellt wird, um die Rolle eines Benutzers zu ändern. Diese Funktion hat die Hilfe der $whichVariablen, die angibt, ob selectdas Formular über oder unter dem Formular erstellt wird, und ermöglicht es, den beiden Dropdowns unterschiedliche nameAttribute zuzuweisen . Leider wird die $whichVariable nicht an die restrict_manage_usersAktion übergeben, daher müssen wir einen anderen Weg finden, um unsere eigenen benutzerdefinierten Elemente zu unterscheiden.

Eine Möglichkeit, dies zu tun, besteht, wie @Linnea vorschlägt , darin, JavaScript hinzuzufügen, um den FilterKlick abzufangen und die Werte der beiden Dropdowns zu synchronisieren. Ich habe eine reine PHP-Lösung gewählt, die ich jetzt beschreiben werde.

Wie man es repariert

Sie können die Möglichkeit nutzen, HTML-Eingaben in Arrays von Werten umzuwandeln und das Array dann zu filtern, um undefinierte Werte zu entfernen. Hier ist der 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 '</select>';
        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

Da ich von PHP 7 begeistert bin, ist für den Fall, dass Sie WP auf einem PHP 7-Server ausführen, eine kürzere, sexyere Version mit dem Null-Koaleszenz-Operator?? :

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 '</select>';
    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' );

Genießen!

morphatisch
quelle
Funktioniert Ihre Lösung nach 4.6.0 noch? Gibt es eine einfachere Möglichkeit, dies mit der neuesten Version von WordPress zu tun? Ich kann anscheinend keine Guides finden, die dieses Jahr hergestellt wurden
Jeremy Muckel
1
@JeremyMuckel Die kurze Antwort auf Ihre Frage lautet "Ja". Meine alte Lösung funktioniert immer noch. Ich verwende es seit Monaten regelmäßig in der Produktion und die meisten meiner Websites werden auf die neueste stabile WP-Version (derzeit 4.9.6) aktualisiert. Davon abgesehen habe ich eine aktualisierte Lösung bereitgestellt, die den neuen Patch verwendet und auch einen subtilen Fehler in meiner vorherigen Lösung behebt.
Morphatic
Dies war hilfreich, aber Ihr Formularcode unter "Wie man es behebt" und "Bonus: PHP 7 Refactor" fehlt. </select>Ich habe auch festgestellt, dass es funktioniert. Ich musste es <form method="get">vor das Auswahlmenü und </form>nach die Filterschaltfläche setzen.
Cogdog
@cogdog guter Fang auf den fehlenden </select>Tags! Ich habe sie hinzugefügt. Seltsam, dass Sie es in eine einschließen mussten, <form>da diese gesamte Seite in einer großen Form verpackt ist und dieser Code in die Mitte eingefügt wird. Ich bin froh, dass es funktioniert hat. :)
morphatic
4

Im Kern sind die unteren Eingabenamen mit der Instanznummer gekennzeichnet, z. B. new_role(oben) und new_role2(unten). Hier sind zwei Ansätze für eine ähnliche Namenskonvention, nämlich course_section1(oben) und course_section2(unten):

Ansatz Nr. 1

Da die $whichVariable ( oben , unten ) nicht an den restrict_manage_usersHook übergeben wird, können wir dies umgehen, indem wir unsere eigene Version dieses Hooks erstellen:

Erstellen wir den Aktions-Hook wpse_restrict_manage_users, der Zugriff auf eine $whichVariable hat:

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

} );

Dann können wir es einhaken mit:

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

    // your stuff here
} );

wo wir jetzt $nameals course_section1am oberen und course_section2am unteren Rand .

Ansatz Nr. 2

Lassen Sie uns restrict_manage_usersDropdowns mit einem anderen Namen für jede Instanz anzeigen:

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

wo wir die Kernfunktion selected()und die Hilfsfunktion verwendet haben:

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

Dann könnten wir dies auch verwenden, wenn wir im pre_get_usersAktionsrückruf nach dem ausgewählten Kursabschnitt suchen.

Birgire
quelle
Dies ist ein faszinierender Ansatz. Ich habe das staticSchlüsselwort noch nie so verwendet (nur innerhalb von Klassen). Wird dabei $instanceeine globale Variable? Müssen Sie sich um Kollisionen mit Variablennamen sorgen? Ich mag auch die Technik, eine neue Aktion zu erstellen, die sich auf eine vorhandene stützt. Vielen Dank!
Morphatic
Dieser Ansatz kann manchmal nützlich sein und wird im Kern verwendet, um beispielsweise Shortcode-Instanzen (Galerie, Wiedergabeliste, Audio) zu zählen. Der statische Variablenbereich hier wird nicht mit dem globalen Variablenbereich in Konflikt geraten. Der Wert der statischen Variablen bleibt zwischen diesen Funktionsaufrufen erhalten, was bei lokalen Variablen nicht der Fall ist. Ich habe dieses nette Tutorial gesucht und gefunden , das mehr Details enthält. @ Morphatic
Birgire
4

Ich habe Ihren Code sowohl in Wordpress 4.4 als auch in Wordpress 4.3.1 getestet. In Version 4.4 habe ich genau das gleiche Problem wie Sie. Ihr Code funktioniert jedoch in Version 4.3.1 korrekt!

Ich denke, das ist ein Wordpress-Fehler. Ich weiß nicht, ob es noch gemeldet wurde. Ich denke, der Grund für den Fehler könnte sein, dass die Schaltfläche "Senden" die Abfragevariablen zweimal sendet. Wenn Sie sich die Abfragevariablen ansehen, werden Sie feststellen, dass course_section zweimal aufgeführt wird, einmal mit dem richtigen Wert und einmal leer.

Bearbeiten: Dies ist die JavaScript-Lösung

Fügen Sie dies einfach der Datei functions.php Ihres Themas hinzu und ändern Sie NAME_OF_YOUR_INPUT_FIELD in den Namen Ihres Eingabefelds! Da WordPress jQuery automatisch auf der Administratorseite lädt, müssen Sie keine Skripte in die Warteschlange stellen. Dieser Codeausschnitt fügt einfach einen Änderungslistener zu den Dropdown-Eingaben hinzu und aktualisiert dann automatisch die andere Dropdown-Liste, um denselben Wert zu erhalten. Mehr Erklärung hier.

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

Hoffe das hilft!

Linnea Huxford
quelle
Danke Linnea. Ja, ich habe das Gleiche festgestellt: Wenn Sie darauf klicken Filter, wird der richtige Wert gesendet, aber dann wird wieder auf die Seite zurückgeleitet, diesmal wird der Wert entfernt. Ich vermute, dass es sich um eine Art Sicherheitsfunktion handelt, die verhindert, dass zufällige, möglicherweise böswillige Werte übermittelt werden, aber ich weiß nicht, wie ich das umgehen soll. Seufzer.
Morphatic
OH! Ich habe herausgefunden, warum der Var zweimal auftaucht. Da es sowohl OBEN als auch UNTEN eine Dropdown-Liste gibt, haben beide Benutzer dasselbe nameAttribut. Wenn ich das Dropdown-Menü UNTER der Tabelle verwende, um die Filterung durchzuführen, funktioniert es wie erwartet. Da dieses Feld nach dem darüber liegenden Feld steht, überschreibt der Nullwert das vorherige. Hmmm ....
morphatisch
Guter Fund! Ich habe versucht herauszufinden, woher das Duplikat kommt. Ich denke, vielleicht könnte ein wenig JavaScript dies beheben. Lassen Sie das andere Dropdown-Menü vor dem Absenden des Formulars auf denselben Wert festlegen.
Linnea Huxford
1

Dies ist eine andere Javascript-Lösung, die für manche Menschen hilfreich sein kann. In meinem Fall habe ich einfach die 2. (untere) Auswahlliste vollständig entfernt. Ich finde, dass ich sowieso nie die unteren Eingänge benutze ...

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
} );
locomo
quelle
1

Nicht-JavaScript-Lösung

Geben Sie der Auswahl einen Namen, der "Array-Stil" ist, wie folgt:

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

Dann werden BEIDE Parameter übergeben (von oben und unten in der Tabelle) und jetzt in einem bekannten Array-Format.

Dann kann der Wert wie folgt in der pre_get_usersFunktion verwendet werden:

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 );
}
zufälliger_Benutzername
quelle
0

eine andere Lösung

Sie können Ihr Filterauswahlfeld in einer separaten Datei wie platzieren user_list_filter.php

und verwenden Sie require_once 'user_list_filter.php'in Ihrer Aktion Rückruffunktion

user_list_filter.php Datei:

<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="">

und in Ihrem Aktionsrückruf:

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