Як відфільтрувати користувачів на сторінці користувачів адміністратора за спеціальним мета-полем?


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

Ця функція змінює запит користувача, щоб додати моє 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-адресу, фільтр працює як очікувалося.

Мій код приблизно базується на цьому коді від Дейв Корта .

Я також спробував додати білий список ваших запитів за допомогою цього коду, але не пощастило:

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

Я використовую WP 4.4. Будь-які ідеї, чому мій фільтр не працює?


FYI, я додав квиток на веб-сайті WP Trac, який не дозволить розробникам перестрибувати будь-який з описаних нижче обручів.
морфатичний

Відповіді:


6

ОНОВЛЕННЯ 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. Використовується найкраща практика для i18n, використовуючи перекладні рядки, наприклад __( 'Filter' )
  3. Обмінялися петлі для (більш модним?) array_map(), array_filter()Іrange()
  4. Використовується sprintf()для генерації шаблонів розмітки
  5. Використовується позначення масиву квадратних дужок замість array()

Нарешті, я виявив помилку в своїх попередніх рішеннях. Ці рішення завжди надають перевагу ТОПУ <select>над БОТОМ <select>. Отже, якщо ви вибрали варіант фільтра із верхнього спаду, а потім виберіть його із нижнього спаду, він все одно використовуватиме лише те значення, яке було вгорі (якщо воно не пусте). Ця нова версія виправляє помилку.

ОНОВЛЕННЯ 2018-02-14

Ця проблема була виправлена ​​з WP 4.6.0, і зміни задокументовані в офіційних документах . Однак рішення, що знаходиться нижче, все ще працює.

Що спричинило проблему (WP <4.6.0)

Проблема полягала в тому, що restrict_manage_usersдію викликають двічі: один раз ПРО ТАБЛИВО таблицю користувачів та один раз НІЖ. Це означає, що ДВІ selectспадні файли створюються з такою ж назвою . Після Filterнатискання кнопки будь-яке значення у другому selectелементі (тобто в нижньому таблиці) перевищує значення у першому, тобто на БЕЗ таблиці.

Якщо ви хочете зануритися в джерело WP, restrict_manage_usersдія запускається зсередини WP_Users_List_Table::extra_tablenav($which), яка є функцією, яка створює нативне спадне меню для зміни ролі користувача. Ця функція має допомогу $whichзмінної, яка повідомляє, чи створює вона selectвище чи нижче форму, і дозволяє їй надати двом спадному меню різні nameатрибути. На жаль, $whichзмінна не передається restrict_manage_usersдії, тому ми повинні придумати інший спосіб диференціювати власні власні елементи.

Один із способів зробити це, як @Linnea пропонує , було б додати трохи JavaScript, щоб зафіксуватиFilter клацання та синхронізувати значення двох спадових меню. Я вибрав лише рішення 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 Refactor

Оскільки я в захваті від PHP 7, якщо ви працюєте з WP на сервері PHP 7, ось коротша, сексуальна версія, використовуючи оператор null coalescing?? :

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? Чи є простіший спосіб зробити це за допомогою новітньої версії wordpress? Я, здається, не можу знайти жодних путівників, зроблених на кшталт цього року
Джеремі Муккель

1
@JeremyMuckel коротка відповідь на ваше запитання - «так». Моє старе рішення все ще працює. Я використовую його у виробництві регулярно вже місяцями, і більшість моїх сайтів оновлюються до останньої стабільної версії WP (наразі 4.9.6). Попри це, я запропонував оновлене рішення, яке використовує новий патч і яке також виправляє тонку помилку в попередньому рішенні.
морфатичний

Це було корисно, але у вашому коді форми в розділі "Як це виправити" та "Бонус: PHP 7 Refactor" відсутній. </select>Я також виявив, що він повинен працювати <form method="get">перед меню вибору та </form>після кнопки фільтра.
cogdog

@cogdog гарний лов на відсутніх </select>тегах! Я їх додав. Дивно, що вам потрібно було обернути його, <form>оскільки вся ця сторінка загорнута в одну велику форму, і цей код вводиться в середину. Рада, що ви все працювали. :)
морфатичний

4

У ядрі найменші вхідні імена позначені номером екземпляра, наприклад new_role(вгорі) та new_role2(знизу). Ось два підходи для подібної конвенції іменування, а саме 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стає глобальною змінною, коли ви це робите? Чи потрібно турбуватися про зіткнення змінних імен? Мені також подобається техніка створення нової дії, що скарбничка спирається на існуючу. Дякую!
морфатичний

Такий підхід іноді може бути зручним і використовується в основі, наприклад, для підрахунку випадків короткого коду (галерея, список відтворення, аудіо). Область статичної змінної тут не зіб’ється з глобальною областю змінної. Значення статичної змінної зберігатиметься між тими викликами функцій, що не стосується локальних змінних. Я шукав і знайшов цей приємний підручник, який має більше деталей. @morphatic
birgire

4

Я перевірив ваш код і в Wordpress 4.4, і в Wordpress 4.3.1. З версією 4.4 я стикаюся з точно таким же питанням, як і ви. Однак ваш код працює правильно у версії 4.3.1!

Я думаю, що це помилка Wordpress. Я не знаю, чи про це ще не повідомлялося. Я думаю, що причина помилки може полягати в тому, що кнопка "Відправити" надсилає запит двічі. Якщо ви подивитесь на кількість запитів, ви побачите, що перелік__секцій перераховано двічі, один раз із правильним значенням та один раз порожнім.

Редагувати: Це рішення JavaScript

Просто додайте це до файлу function.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нього надсилається правильне значення, але потім перенаправляється назад на сторінку знову, цього разу знімаючи значення. Я здогадуюсь, що це якась "особливість" безпеки для запобігання надсилання випадкових, потенційно шкідливих цінностей, але я не знаю, як це обійти. Зітхнути.
морфатичний

О! Я зрозумів, чому вар з'являється двічі. Оскільки є спадне меню як НАЗАД, так і внизу таблиці користувачів, і вони мають однаковий nameатрибут. Якщо я використовую випадаючу таблицю нижче, щоб виконати фільтрацію, вона працює як очікувалося. Оскільки це поле надходить за полем над ним, воно має нульове значення, яке перевершує попереднє. Хммм ....
морфатична

Гарна знахідка! Я намагався з’ясувати, звідки надходить дублікат. Я думаю, можливо, трохи JavaScript може це виправити. Перед тим, як подати форму, встановіть інше спадне меню як таке ж значення.
Ліннея Хаксфорд

1

Це інше рішення 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
} );

1

Рішення без JavaScript

Укажіть назву вибору, яка є "в масиві", наприклад:

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

Потім передаються параметри BOTH (зверху та ботом таблиці), і тепер у відомому форматі масиву.

Тоді значення можна використовувати у 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';
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.