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


9

問題

WPは、ユーザーのリストのフィルター処理に使用される前に、クエリ変数の値を削除するように見えます。

私のコード

この関数は、ユーザーテーブルにカスタム列を追加します/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' );

この関数は、列に値を入力する方法を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' );

これにより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' );

この関数は、ユーザークエリを変更してmy 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選択した値が取り除かれているようです。

私が提出した場合はcourse_section、URLに直接入力して、変数を予想通り、フィルタが動作します。

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

私はこのコードを使用してクエリ変数をホワイトリストに登録しようとしましたが、運が悪かったです:

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

WP 4.4を使用しています。フィルターが機能しない理由はありますか?


ちなみに、私はWP Tracサイトにチケット追加しました。これにより、開発者が以下に説明するフープのすべてを通過する必要がなくなります。
2016年

回答:


6

2018年6月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. 翻訳可能な文字列を使用してi18nのベストプラクティスを使用。 __( 'Filter' )
  3. 交換用のループ(よりファッショナブルな?) array_map()array_filter()と、range()
  4. sprintf()マークアップテンプレートの生成に使用
  5. 代わりに角括弧配列表記を使用 array()

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

更新2018-02-14

この問題はWP 4.6.0以降パッチされており、変更は公式ドキュメントに記載されています。ただし、以下の解決策はまだ機能します。

問題の原因(WP <4.6.0)

問題は、restrict_manage_usersアクションが2回呼び出されることでした。1回はユーザーテーブルの上、1回はその下です。つまり、2つのselectドロップダウンが同じ名前で作成されます。ときにFilterボタンをクリックすると、どのような値は秒であるselect(すなわち、以下の表1)は、すなわちテーブル上記のもの、最初のものの値を上書き要素。

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

@Linneaが示唆するように、これを行う1つの方法、JavaScriptを追加してFilterクリックをキャッチし、2つのドロップダウンの値を同期させることです。ここで説明する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 '</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' );

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

私がPHP 7に興奮しているので、PHP 7サーバーでWPを実行している場合に備えて、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 '</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' );

楽しい!


では、あなたのソリューションは4.6.0以降も機能しますか?ワードプレスの最新バージョンでそれを行う簡単な方法はありますか?今年のようなガイドは見つけられないようです
ジェレミー・マッケル

1
@JeremyMuckelあなたの質問への短い答えは「はい」です。私の古い解決策はまだ機能します。私は数か月間、本番環境で定期的に使用しており、私のサイトのほとんどは最新の安定したWPバージョン(現在は4.9.6)に更新されています。そうは言っても、新しいパッチを使用し、以前のソリューションの微妙なバグも修正する更新されたソリューションを提供しました。
モーフィック2018年

これは役に立ちましたが、「修正方法」および「ボーナス:PHP 7リファクタリング」の下のフォームコードが欠落し</select>ています。これを機能させるには<form method="get">、選択メニューの前と</form>フィルターボタンの後に配置する必要がありました。
cogdog

@cogdogは、不足している</select>タグをうまくキャッチします!追加しました。奇妙なことに、<form>このページ全体が1つの大きなフォームでラップされ、このコードがその中央に挿入されるため、それをラップする必要がありました。しかし、あなたがそれを機能させてうれしい。:)
モーフィック

4

コアでは、下部の入力名はインスタンス番号でマークされますnew_role(例:(top)とnew_role2(bottom))。同様の命名規則に対する2つのアプローチ、つまりcourse_section1(上)とcourse_section2(下)は次のとおりです。

アプローチ#1

以来$which変数(トップに渡されません)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
} );

どこ我々が今持っている$nameようcourse_section1、トップcourse_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アクションコールバックで選択されたコースセクションをチェックするときにもこれを使用できます。


これは魅力的なアプローチです。staticこの方法でキーワードを使用したことはありません(クラス内でのみ)。$instanceあなたがこれを行うときに、グローバル変数になりますか?変数名の衝突を心配する必要がありますか?また、既存のアクションを基に新しいアクションを作成する手法も気に入っています。ありがとう!
2016年

このアプローチは便利な場合があり、たとえば、ショートコード(ギャラリー、プレイリスト、オーディオ)インスタンスをカウントするためにコアで使用されます。ここでの静的変数スコープは、グローバル変数スコープを混乱させることはありません。静的変数の値は、これらの関数呼び出し間で保持されますが、ローカル変数の場合は異なります。詳細を確認できるこの素晴らしいチュートリアルを検索して見つけました。@morphatic
16

4

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

これはWordpressのバグだと思います。それがまだ報告されているかどうかはわかりません。バグの背後にある理由は、送信ボタンがクエリ変数を2回送信しているためかもしれません。クエリ変数を見ると、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
} );

お役に立てれば!


リネアに感謝します。はい、同じことがわかりました。クリックFilterすると正しい値が送信されますが、再度ページにリダイレクトされ、今度は値が削除されます。私の推測では、ランダムな、潜在的に悪意のある値が送信されるのを防ぐのはある種のセキュリティ「機能」であると思いますが、私はそれを回避する方法がわかりません。はぁ。
2016年

ああ!変数が2回表示される理由を理解しました。ABOVEとBELOWの両方のドロップダウンがあるため、usersテーブルには両方のname属性が同じです。テーブルの下のドロップダウンを使用してフィルタリングを実行すると、期待どおりに機能します。そのフィールドはその上のフィールドの後に来るため、ヌル値は以前のフィールドをオーバーライドします。
うーん

良い発見!重複がどこから来ているのかを把握しようとしていました。少しJavaScriptでこれを修正できると思います。フォームを送信する前に、他のドロップダウンを同じ値に設定してください。
Linnea Huxford 2016年

1

これは、一部の人に役立つ可能性がある別の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

非JavaScriptソリューション

次のように、selectに「配列スタイル」の名前を付けます。

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

0

別の解決策

あなたはあなたのようなフィルター選択ボックスを別のファイルに置くことができます 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';
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.