如何通过自定义元字段过滤管理用户页面上的用户?

时间:2016-01-04 作者:morphatic

问题是,在我的查询变量用于过滤用户列表之前,它的值被删除了。

My Code(我的代码)

此函数将自定义列添加到/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\' );
此函数更改用户查询以添加我的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中键入变量,过滤器将按预期工作。

我的代码大致基于this code from 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。你知道为什么我的过滤器不工作吗?

6 个回复
最合适的回答,由SO网友:morphatic 整理而成

UPDATE 2018-06-28

While the code below mostly works fine, here is a rewrite of the code for WP >=4.6.0 (using 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\');

I incorporated several ideas from @birgire and @cale_b who also offers solutions below that are worth reading. Specifically, I:

  1. Used the $which variable that was added in v4.6.0
  2. Used best practice for i18n by using translatable strings, e.g. __( \'Filter\' )
  3. Exchanged loops for the (more fashionable?) array_map(), array_filter(), and range()
  4. Used sprintf() for generating the markup templates
  5. Used the square bracket array notation instead of array()

Lastly, I discovered a bug in my earlier solutions. Those solutions always favor the TOP <select> over the BOTTOM <select>. So if you selected a filter option from the top dropdown, and then subsequently select one from the bottom dropdown, the filter will still only use whatever value was up top (if it\'s not blank). This new version corrects that bug.

UPDATE 2018-02-14

This issue has been patched since WP 4.6.0 and the changes are documented in the official docs. The solution below still works, though.

What Caused the Problem (WP <4.6.0)

The problem was that the restrict_manage_users action gets called twice: once ABOVE the Users table, and once BELOW it. This means that TWO select dropdowns get created with the same name. When the Filter button is clicked, whatever value is in the second select element (i.e. the one BELOW the table) overrides the value in the first one, i.e. the one ABOVE the table.

In case you want to dive into the WP source, the restrict_manage_users action is triggered from within WP_Users_List_Table::extra_tablenav($which), which is the function that creates the native dropdown to change a user\'s role. That function has the help of the $which variable that tells it whether it is creating the select above or below the form, and allows it to give the two dropdowns different name attributes. Unfortunately, the $which variable doesn\'t get passed to the restrict_manage_users action, so we have to come up with another way to differentiate our own custom elements.

One way to do this, as @Linnea suggests, would be to add some JavaScript to catch the Filter click and sync up the values of the two dropdowns. I chose a PHP-only solution that I\'ll describe now.

How to Fix It

You can take advantage of the ability to turn HTML inputs into arrays of values, and then filter the array to get rid of any undefined values. Here\'s the 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

Since I\'m excited about PHP 7, in case you\'re running WP on a PHP 7 server, here\'s a shorter, sexier version using the null coalescing 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\' );

Enjoy!

SO网友:Linnea Huxford

我在Wordpress 4.4和Wordpress 4.3.1中测试了您的代码。在4.4版中,我遇到了与您完全相同的问题。但是,您的代码在版本4.3.1中工作正常!

我认为这是一个Wordpress bug。我不知道是否有报道。我认为这个bug背后的原因可能是submit按钮发送了两次查询变量。如果查看查询变量,您将看到course\\u部分列出了两次,一次使用正确的值,一次为空。

编辑:这是JavaScript解决方案,只需将其添加到主题的函数中即可。php文件并将\\u YOUR\\u INPUT\\u字段的名称更改为输入字段的名称!由于WordPress会在管理端自动加载jQuery,因此不必将任何脚本排队。这段代码只是向下拉输入中添加一个更改侦听器,然后自动更新另一个下拉列表以匹配相同的值。More explanation here.

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
} );
希望这有帮助!

SO网友:birgire

在core中,底部输入名称用实例号标记,例如。new_role (顶部)和new_role2 (底部)。这里有两种类似命名约定的方法,即course_section1 (顶部)和course_section2 (底部):

$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
} );
我们现在拥有的$namecourse_section1 在顶部course_section2 在底部。

接近#2restrict_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 操作回调。

SO网友:locomo

这是一个不同的Javascript解决方案,可能对某些人有所帮助。在我的例子中,我只是完全删除了第二个(底部)选择列表。我发现我从来没有使用过底部输入。。。

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

SO网友:random_user_name

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

SO网友:Alpha Elf

另一种解决方案是,您可以将过滤器选择框放入单独的文件中,如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\';
}

相关推荐

WordPress网站损坏,链接失效,无法访问wp-admin页面

我刚刚收到一个坏掉的Wordpress网站,我现在负责管理这个网站。编辑:自从发布这篇文章以来,我对自己的问题有了一些新的发现。当尝试访问Wordpress管理页面(通过单击登录仪表板的链接)时,我得到一个404未找到页面。此404错误页面与所托管网站上的所有其他页面具有相同的品牌(背景和样式)。我猜我的链接断了。网站上的身份验证页面不起作用,我无法访问前面所述的管理门户。我有权访问根目录和wp content文件夹。该页面由wordpress托管,我通过启用sftp访问root。我还可以访问phpMyA