Bagaimana cara memfilter pengguna pada halaman pengguna admin berdasarkan bidang meta khusus?


9

Masalah

WP muncul untuk menghapus nilai variabel kueri saya sebelum digunakan untuk memfilter daftar pengguna.

Kode Saya

Fungsi ini menambahkan kolom khusus ke tabel Pengguna saya di /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' );

Fungsi ini memberi tahu WP cara mengisi nilai di kolom:

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

Ini menambahkan dropdown dan Filtertombol di atas tabel Users:

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

Fungsi ini mengubah permintaan pengguna untuk menambahkan 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' );

Informasi lainnya

Itu membuat dropdown saya dengan benar. Ketika saya memilih bagian kursus dan mengklik Filterhalaman menyegarkan dan course_sectionmuncul di URL, tetapi tidak memiliki nilai yang terkait dengannya. Jika saya memeriksa permintaan HTTP, itu menunjukkan bahwa itu akan dikirimkan dengan nilai variabel yang benar, tetapi kemudian ada302 Redirect yang tampaknya menghapus nilai yang saya pilih.

Jika saya kirimkan course_section variabel dengan mengetikkannya langsung ke URL, filter berfungsi seperti yang diharapkan.

Kode saya secara kasar didasarkan pada kode ini dari Dave Court .

Saya juga mencoba memasukkan var dalam daftar putih menggunakan kode ini, tetapi tidak berhasil:

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

Saya menggunakan WP 4.4. Adakah gagasan mengapa filter saya tidak berfungsi?


FYI, saya menambahkan tiket di situs WP Trac yang akan mencegah pengembang dari harus melewati rintangan yang dijelaskan di bawah ini.
Morfatik

Jawaban:


6

UPDATE 2018-06-28

Sementara kode di bawah sebagian besar berfungsi dengan baik, berikut adalah penulisan ulang kode untuk WP> = 4.6.0 (menggunakan 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');

Saya memasukkan beberapa ide dari @birgire dan @cale_b yang juga menawarkan solusi di bawah ini yang layak dibaca. Secara khusus, saya:

  1. Menggunakan $whichvariabel yang ditambahkan div4.6.0
  2. Digunakan praktik terbaik untuk i18n dengan menggunakan string yang dapat diterjemahkan, misalnya __( 'Filter' )
  3. Ditukar loop untuk (lebih modis?) array_map(), array_filter()Danrange()
  4. Digunakan sprintf()untuk membuat templat markup
  5. Digunakan notasi array braket persegi bukan array()

Terakhir, saya menemukan bug dalam solusi saya sebelumnya. Solusi-solusi itu selalu mendukung TOP <select>daripada BOTTOM <select>. Jadi jika Anda memilih opsi filter dari dropdown atas, dan kemudian memilih satu dari dropdown bawah, filter masih akan hanya menggunakan nilai apa pun yang ada di atas (jika tidak kosong). Versi baru ini memperbaiki bug itu.

PEMBARUAN 2018-02-14

Ini masalah telah ditambal sejak WP 4.6.0 dan perubahan didokumentasikan dalam dokumen resmi . Namun, solusi di bawah masih berfungsi.

Apa yang Menyebabkan Masalah (WP <4.6.0)

Masalahnya adalah bahwa restrict_manage_userstindakan dipanggil dua kali: sekali DI ATAS tabel Pengguna, dan sekali DI BAWAH. Ini berarti bahwa DUA selectdropdown dibuat dengan nama yang sama . Ketika Filtertombol diklik, nilai apa pun yang ada di selectelemen kedua (yaitu yang DI BAWAH tabel) menimpa nilai di yang pertama, yaitu yang di atas tabel.

Jika Anda ingin masuk ke sumber WP, restrict_manage_userstindakan dipicu dari dalam WP_Users_List_Table::extra_tablenav($which), yang merupakan fungsi yang menciptakan dropdown asli untuk mengubah peran pengguna. Fungsi itu memiliki bantuan dari $whichvariabel yang memberitahukannya apakah itu membuat selectformulir di atas atau di bawah, dan memungkinkannya untuk memberikan dua dropdown nameatribut yang berbeda . Sayangnya, $whichvariabel tidak diteruskan ke restrict_manage_userstindakan, jadi kami harus menemukan cara lain untuk membedakan elemen khusus kami.

Salah satu cara untuk melakukan ini, seperti yang disarankan oleh @Linnea , adalah menambahkan beberapa JavaScript untuk menangkap Filterklik dan menyinkronkan nilai-nilai dari kedua dropdown. Saya memilih solusi khusus PHP yang akan saya jelaskan sekarang.

Bagaimana memperbaikinya

Anda dapat memanfaatkan kemampuan untuk mengubah input HTML menjadi array nilai, dan kemudian menyaring array untuk menghilangkan nilai yang tidak ditentukan. Berikut kodenya:

    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

Karena saya senang dengan PHP 7, jika Anda menjalankan WP pada server PHP 7, inilah versi yang lebih pendek dan seksi menggunakan operator penggabungan nol?? :

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

Nikmati!


Jadi apakah solusi Anda masih berfungsi setelah 4.6.0? Apakah ada cara yang lebih mudah untuk melakukannya dengan versi terbaru dari wordpress? Saya tidak bisa menemukan pemandu yang dibuat seperti, tahun ini
Jeremy Muckel

1
@ JeremyMuckel jawaban singkat untuk pertanyaan Anda adalah "ya." Solusi lama saya masih berfungsi. Saya telah menggunakannya dalam produksi secara teratur selama berbulan-bulan sekarang dan sebagian besar situs saya diperbarui ke versi WP stabil terbaru (saat ini 4.9.6). Yang sedang berkata, saya telah memberikan solusi yang diperbarui yang menggunakan tambalan baru dan yang juga memperbaiki bug halus dalam solusi saya sebelumnya.
morfatik

Ini sangat membantu tapi kode formulir Anda di bawah "Bagaimana Fix It" dan "Bonus: PHP 7 Refactor" hilang satu </select>Saya juga menemukan untuk mendapatkannya bekerja aku harus meletakkan <form method="get">sebelum pilih menu dan </form>setelah filter tombol.
cogdog

@cogdog tangkapan bagus pada </select>tag yang hilang ! Saya menambahkannya. Aneh bahwa Anda perlu membungkusnya <form>karena seluruh halaman ini dibungkus dalam satu bentuk besar, dan kode ini akan disuntikkan ke tengah-tengahnya. Senang Anda berhasil, meskipun. :)
morphatic

4

Pada intinya, nama input bawah ditandai dengan nomor instance, mis. new_role( Atas ) dan new_role2(bawah). Berikut adalah dua pendekatan untuk konvensi penamaan yang serupa, yaitu course_section1(atas) dan course_section2(bawah):

Pendekatan # 1

Karena $whichvariabel ( atas , bawah ) tidak diteruskan ke restrict_manage_usershook, kita dapat menyiasatinya dengan membuat versi kita sendiri dari hook itu:

Mari kita membuat kait tindakan wpse_restrict_manage_usersyang memiliki akses ke $whichvariabel:

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

} );

Lalu kita bisa mengaitkannya dengan:

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

    // your stuff here
} );

di mana kita sekarang memiliki $namesebagai course_section1di atas dan course_section2di bawah .

Pendekatan # 2

Mari menghubungkan restrict_manage_users, untuk menampilkan dropdown, dengan nama yang berbeda untuk setiap 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' );

tempat kami menggunakan fungsi inti selected()dan fungsi pembantu:

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

Kemudian kita juga bisa menggunakan ini ketika kita memeriksa bagian kursus yang dipilih di pre_get_userscallback tindakan.


Ini adalah pendekatan yang menarik. Saya tidak pernah menggunakan statickata kunci dengan cara ini (hanya di dalam kelas). Apakah $instancemenjadi variabel global saat Anda melakukan ini? Apakah Anda harus khawatir tentang tabrakan nama variabel? Saya juga suka teknik menciptakan tindakan baru yang mendukung yang sudah ada. Terima kasih!
Morfatik

Pendekatan ini kadang-kadang berguna dan digunakan dalam inti untuk misalnya menghitung instance shortcode (galeri, daftar putar, audio). Ruang lingkup variabel statis di sini tidak akan mengacaukan ruang lingkup variabel global. Nilai variabel statis akan dipertahankan antara panggilan fungsi tersebut, yang tidak berlaku untuk variabel lokal. Saya mencari dan menemukan tutorial yang bagus ini yang memiliki lebih banyak detail. @morphatic
birgire

4

Saya menguji kode Anda di Wordpress 4.4 dan Wordpress 4.3.1. Dengan versi 4.4, saya menemukan masalah yang persis sama dengan Anda. Namun, kode Anda berfungsi dengan benar dalam versi 4.3.1!

Saya pikir ini adalah bug Wordpress. Saya tidak tahu apakah ini sudah dilaporkan. Saya pikir alasan di balik bug mungkin karena tombol kirim mengirimkan vars permintaan dua kali. Jika Anda melihat kueri vars, Anda akan melihat bahwa course_section terdaftar dua kali, sekali dengan nilai yang benar dan sekali kosong.

Sunting: Ini adalah Solusi JavaScript

Cukup tambahkan ini ke file functions.php tema Anda dan ubah NAME_OF_YOUR_INPUT_FIELD menjadi nama bidang input Anda! Karena WordPress secara otomatis memuat jQuery di sisi admin, Anda tidak perlu membuat skrip apa pun. Potongan kode ini hanya menambah pendengar perubahan ke input dropdown dan kemudian secara otomatis memperbarui dropdown lainnya agar sesuai dengan nilai yang sama. Penjelasan lebih lanjut di sini.

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

Semoga ini membantu!


Terima kasih Linnea. Ya, saya menemukan hal yang sama, bahwa ketika Anda mengkliknya Filtermengirimkan nilai yang benar, tetapi kemudian mengarahkan kembali ke halaman lagi, kali ini membuang nilainya. Dugaan saya adalah bahwa ini adalah semacam "fitur" keamanan untuk mencegah nilai-nilai acak, yang berpotensi berbahaya, dikirimkan, tetapi saya tidak tahu bagaimana cara mengatasinya. Mendesah.
morphatic

OH! Saya tahu mengapa var muncul dua kali. Karena ada dropdown baik DI ATAS dan DI BAWAH tabel pengguna dan keduanya memiliki nameatribut yang sama . Jika saya menggunakan dropdown DI BAWAH tabel untuk melakukan penyaringan, ini berfungsi seperti yang diharapkan. Karena bidang itu muncul setelah yang di atasnya, nilainya nol menimpa yang sebelumnya. Hmmm ....
morphatic

Bagus temukan! Saya mencoba mencari tahu dari mana duplikat itu berasal. Saya pikir mungkin sedikit JavaScript yang bisa memperbaikinya. Apakah itu menetapkan drop down yang lain sebagai nilai yang sama sebelum mengirimkan formulir.
Linnea Huxford

1

Ini adalah solusi Javascript berbeda yang mungkin bermanfaat bagi sebagian orang. Dalam kasus saya, saya hanya menghapus daftar pilih ke-2 (bawah) seluruhnya. Saya menemukan bahwa saya tidak pernah menggunakan input terbawah ...

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

Solusi Non-JavaScript

Beri nama yang dipilih "gaya array", seperti:

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

Kemudian KEDUA parameter dilewatkan (dari atas dan bawah tabel), dan sekarang dalam format array yang dikenal.

Kemudian, nilai tersebut dapat digunakan seperti ini dalam pre_get_usersfungsi:

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

0

solusi lain

Anda dapat menempatkan kotak pilih filter Anda dalam file terpisah seperti user_list_filter.php

dan gunakan require_once 'user_list_filter.php'dalam fungsi callback tindakan Anda

user_list_filter.php mengajukan:

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

dan dalam callback tindakan Anda:

function add_course_section_filter() {
    require_once 'user_list_filter.php';
}
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.