web-dev-qa-db-ja.com

カスタムメタフィールドで管理ユーザーページのユーザーをフィルタする方法

問題

WPは、ユーザーのリストをフィルタリングするために使用される前に、私のクエリ変数の値を削除しているようです。

私のコード

この関数は/wp-admin/users.phpの私のUsersテーブルにカスタム列を追加します。

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

この関数はWPに列の値を埋める方法を指示します。

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

これは、Usersテーブルの上にドロップダウンとFilterボタンを追加します。

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

この関数は私の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' );

その他の情報

それは私のドロップダウンを正しく作成します。コースのセクションを選択してFilterをクリックすると、ページが更新されてcourse_sectionがURLに表示されますが、それに関連付けられた値はありません。 HTTPリクエストを確認すると、正しい変数値で送信されていることがわかりますが、選択した値が削除されるように思われる302 Redirectがあります。

URLに直接入力してcourse_section変数を送信した場合、フィルタは期待どおりに機能します。

私のコードはおおよそ Dave Courtからのこのコード に基づいています。

私はまた、このコードを使用して自分のクエリvarをホワイトリストに登録しようとしましたが、うまくいきませんでした。

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

私はWP 4.4を使っています。私のフィルタが機能しない理由は何ですか?

9
morphatic

アップデート2018-06-28

以下のコードは大体うまくいきますが、WP> = 4.6.0(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');

私は@birgireと@cale_bからいくつかのアイデアを取り入れました。それらもまた読む価値のある解決策を提供します。具体的には、

  1. $whichに追加されたv4.6.0変数を使用しました
  2. 翻訳可能な文字列を使用して国際化のベストプラクティスを使用__( 'Filter' )
  3. array_map()array_filter()range()のループを交換しました。
  4. マークアップテンプレートの生成にsprintf()を使用しました
  5. array()の代わりに角括弧配列表記を使用しました

最後に、私は以前のソリューションでバグを発見しました。これらのソリューションは、常にBOTTOMの<select>よりもTOPの<select>を優先します。そのため、一番上のドロップダウンからフィルターオプションを選択し、次に一番下のドロップダウンからフィルターオプションを選択した場合でも、フィルターは一番上にある値のみを使用します(空白でない場合)。この新しいバージョンはそのバグを修正します。

アップデート2018-02-14

この issueはWP 4.6.0 からパッチが適用され、 の変更点は公式文書 に文書化されています。しかし、以下の解決策はまだうまくいきます。

問題の原因(WP <4.6.0)

問題は、restrict_manage_usersアクションが2回呼び出されることです。1回はUsersテーブルの上に、もう1回はその下にあります。これは、2つのselectドロップダウンが 同じ名前 で作成されることを意味します。 Filterボタンがクリックされると、2番目のselect要素の値(テーブルの下の値)が最初の要素の値、つまりテーブルの上の値をオーバーライドします。

WPソースに飛び込む場合は、 WP_Users_List_Table::extra_tablenav($which) 内からrestrict_manage_usersアクションがトリガーされます。これは、ユーザーの役割を変更するためのネイティブドロップダウンを作成する関数です。この関数は、フォームの上または下にselectを作成しているかどうかを示し、2つのドロップダウンに異なるname属性を指定できるようにする$which変数の助けを借りています。残念ながら、$which変数はrestrict_manage_usersアクションに渡されないため、独自のカスタム要素を区別する別の方法を考え出す必要があります。

@Linneaが上で示唆しているように、これを行うための1つの方法はFilterクリックをキャッチして2つのドロップダウンの値を同期させるためにJavaScriptを追加することです。私はこれから説明するPHP専用のソリューションを選びました。

それを修正する方法

HTML入力を値の配列に変換してから、配列をフィルタリングして未定義の値を削除する機能を利用できます。これがコードです:

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

ボーナス:PHP 7リファクタリング

私はPHP 7に興奮しているので、WP 7をPHP 7サーバーで実行している場合は、ここで を使用したより短くセクシーなバージョンになります。 null合体演算子??

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

楽しい!

6
morphatic

Wordpress 4.4とWordpress 4.3.1の両方でコードをテストしました。バージョン4.4では、あなたとまったく同じ問題に遭遇します。しかし、あなたのコードはバージョン4.3.1では正しく動作します。

これはWordpressのバグだと思います。まだ報告されているかどうかはわかりません。このバグの背後にある理由は、送信ボタンがクエリ変数を2回送信していることにあると思います。クエリvarsを見ると、course_sectionが2回リストされています。1回は正しい値で、もう1回は空です。

編集:これはJavaScriptのソリューションです

これをあなたのテーマのfunctions.phpファイルに追加し、NAME_OF_YOUR_INPUT_FIELDをあなたの入力フィールドの名前に変更するだけです! WordPressは自動的にjQueryを管理者側にロードするので、スクリプトをエンキューする必要はありません。このコードでは、ドロップダウン入力に変更リスナーを追加してから、もう一方のドロップダウンを同じ値に一致するように自動的に更新しています。 もっと説明がここにあります。

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

お役に立てれば!

4
Linnea Huxford

コアでは、bottom入力名はインスタンス番号でマークされています。 new_role(上)およびnew_role2(下)。同様の命名規則、つまりcourse_section1(上)とcourse_section2(下)の2つのアプローチを次に示します。

アプローチ#1

$which変数(topbottom)はrestrict_manage_usersフックに渡されないため、独自のバージョンのフックを作成することで回避できます。

wpse_restrict_manage_users変数にアクセスできるアクションフック$whichを作成しましょう。

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

} );

次に、次のものでフックできます。

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

    // your stuff here
} );

top$nameとしてcourse_section1があり、bottomcourse_section2があります)。

アプローチ#2

restrict_manage_usersにフックして、インスタンスごとに異なる名前のドロップダウンを表示します。

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

ここでコア関数 selected() とヘルパー関数を使用しました:

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

次に、pre_get_usersアクションコールバックで選択したコースセクションを確認するときにこれを使用することもできます。

4
birgire

これは別のJavascriptソリューションで、一部の人にとっては役に立つかもしれません。私の場合は、2番目(一番下)の選択リストを完全に削除しました。とにかく一番下の入力を使わないことに気づきました….

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

非JavaScriptソリューション

次のように、「配列スタイル」の名前を選択します。

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

次に、両方のパラメータが(表の上と下から)渡され、既知の配列形式になります。

それから、値は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

別の解決策

あなたはuser_list_filter.phpのような別のファイルにあなたのフィルター選択ボックスを置くことができます

アクションコールバック関数でrequire_once 'user_list_filter.php'を使う

user_list_filter.phpファイル:

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

そしてあなたのアクションコールバックで:

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